docs / core / list
List
a vertical stack of items, each with an optional marker. the marker is decoration, so the text carries the meaning on its own.
intensity · recedesrsc · client
provenance
authored in usvalayer
core / primitivesintensity
recedes · safe anywhere, including dense surfacescomposition
feature lists, checklists and summaries inside a Card or sectiondivided when the rows are entries, plain when they are bulletsnot a menu and not navigation. those have their own rolesmeaning never lives in the marker alonea11y
a realul or ol · every marker is aria-hiddenjest-axelive demo · try it out
- Runs entirely on your machine
- No tracking, no analytics
- Open source, end to end
customize
asul or ol, by whether order carries meaning
markerdecorative marker on every item
dividedrule between items, not after the last
usage
import { List, ListItem } from "usva/primitives/list";
<List marker={<CheckIcon />} divided>
<ListItem>Runs entirely on your machine</ListItem>
<ListItem>No tracking, no analytics</ListItem>
<ListItem>Open source, end to end</ListItem>
</List>props
| prop | type | default | notes |
|---|---|---|---|
| as | "ul" | "ol" | "ul" | pick by whether the order carries meaning. |
| marker | ReactNode | — | decorative marker on every item, hidden from assistive tech. |
| divided | boolean | false | rule between items, stopping at the last one. |
| ListItem marker | ReactNode | — | overrides the shared marker for this item alone. |
get it
npx shadcn add https://usva.build/r/list.jsonsource · components/ui/list.tsxexactly what this command copies
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
const ListContext = React.createContext<React.ReactNode>(null);
export interface ListProps extends React.HTMLAttributes<HTMLElement> {
as?: "ul" | "ol";
/** Decorative marker rendered on every item. An item may override it. */
marker?: React.ReactNode;
/** Rule between items, stopping at the last one. */
divided?: boolean;
}
export const List = React.forwardRef<HTMLElement, ListProps>(
(
{ className, as: Comp = "ul", marker, divided, children, ...props },
ref,
) => (
<ListContext.Provider value={marker ?? null}>
<Comp
ref={ref as React.Ref<HTMLUListElement & HTMLOListElement>}
className={cn(
"flex list-none flex-col",
divided &&
"[&>li]:border-b [&>li]:border-border [&>li:last-child]:border-0",
className,
)}
{...props}
>
{children}
</Comp>
</ListContext.Provider>
),
);
List.displayName = "List";
export interface ListItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
/** Overrides the list's shared marker for this item alone. */
marker?: React.ReactNode;
}
export const ListItem = React.forwardRef<HTMLLIElement, ListItemProps>(
({ className, marker, children, ...props }, ref) => {
const shared = React.useContext(ListContext);
const shown = marker ?? shared;
return (
<li
ref={ref}
className={cn(
"flex items-start gap-3 px-3 py-4 text-muted",
"transition-tint duration-fast ease-soft hover:text-ink motion-reduce:transition-none",
className,
)}
{...props}
>
{shown != null && (
<span
data-list-marker=""
aria-hidden="true"
className="mt-0.5 shrink-0 text-accent-alt [&_svg]:size-4"
>
{shown}
</span>
)}
<span className="min-w-0 flex-1">{children}</span>
</li>
);
},
);
ListItem.displayName = "ListItem";