docs / patterns / checklist-card

ChecklistCard

a card of short reassurances, each one ticked. for the feature and privacy claims on a landing page, three to five at a time.

intensity · structuresrsc · server

provenance

shaped from sisu-plus

layer

core / patterns

intensity

structures · organizes a region, stays out of the content

composition

feature and privacy claims on landing pages, three to five itemsa custom marker when the tick reads wrong for the contentnot a todo list. nothing here is interactive or checkableno paragraphs as items. a claim is one line

a11y

a real ul with the item count · markers are aria-hidden · the title is a headingjest-axe

dependencies

Card · List from the same package
live demo · try it out

Privacy

  • Runs entirely on your machine
  • No tracking, no analytics, no accounts
  • Open source, end to end
customize
titleheading above the list, empty to omit
markerdecorative glyph before each item
usagecopy
import { ChecklistCard } from "usva/patterns/checklist-card";

<ChecklistCard
  title="Privacy"
  items={["Runs entirely on your machine", "No tracking, no analytics, no accounts", "Open source, end to end"]}
/>

props

proptypedefaultnotes
itemsReact.ReactNode[]the claims. strings or nodes.
titleReact.ReactNodeheading above the list. omitted entirely when absent.
markerReact.ReactNodetickoverrides the default tick. decorative either way.

get it

npx shadcn add https://usva.build/r/checklist-card.jsoncopy
source · components/ui/checklist-card.tsxexactly what this command copiescopy
import * as React from "react";
import { cn } from "@/lib/utils";
import { Card } from "./card";
import { List, ListItem } from "./list";

export interface ChecklistCardProps
  extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
  title?: React.ReactNode;
  items: React.ReactNode[];
  /** Defaults to a tick. Decorative either way. */
  marker?: React.ReactNode;
}

/**
 * A card of reassurances: short claims, each ticked. sisu renders these as bare divs
 * with a bottom border, which reads to a screen reader as loose prose rather than a
 * set of three related items.
 */
export const ChecklistCard = React.forwardRef<
  HTMLDivElement,
  ChecklistCardProps
>(({ className, title, items, marker, ...props }, ref) => (
  <Card ref={ref} className={cn("p-4", className)} {...props}>
    {title != null && (
      <h3 className="px-3 pt-1 pb-2 text-base font-semibold tracking-[-0.01em] text-ink">
        {title}
      </h3>
    )}
    <List marker={marker ?? <TickIcon />} divided>
      {items.map((item, index) => (
        <ListItem key={typeof item === "string" ? item : index}>
          {item}
        </ListItem>
      ))}
    </List>
  </Card>
));
ChecklistCard.displayName = "ChecklistCard";

function TickIcon() {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2.25"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      <path d="M20 6 9 17l-5-5" />
    </svg>
  );
}