All components
List
Ordered and unordered lists with native markers or custom icon indicators.
- Compiles to static CSS
- Zero runtime style objects
- Types for every token
123456789101112131415161718192021222324252627import { List } from "#components/ui/base-ui/list.tsx";
const features = [ "Compiles to static CSS", "Zero runtime style objects", "Types for every token",];
export function Example() { return ( // variant resolves once on Root; items inherit it via context. // "marker" uses native list markers; "plain" swaps them for indicators. <List.Root variant="plain"> {features.map((text) => ( <List.Item key={text}> <List.Indicator> <CheckIcon /> </List.Indicator> {text} </List.Item> ))} </List.Root> );}
// ordered lists compose through the render prop:// <List.Root render={<ol />}>…</List.Root>Variants
- Compiles to static CSS
- Zero runtime style objects
- Types for every token
- Compiles to static CSS
- Zero runtime style objects
- Types for every token
Install
1npx stylexblocks add listSource
components/ui/base-ui/list.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596import * as stylex from "@stylexjs/stylex";import { createSlotRecipeContext } from "#lib/base-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe } from "#lib/recipe-props.ts";const listRoot = stylex.create({ base: { display: 'flex', flexDirection: 'column', gap: 'var(--list-gap)', },})
const listRootVariants = stylex.create({ marker: { listStyle: 'revert', listStylePosition: 'inside', },})
const listItem = stylex.create({ base: { whiteSpace: 'normal', display: 'list-item', },})
const listItemVariants = stylex.create({ marker: {}, plain: { alignItems: 'flex-start', display: 'inline-flex', },})
const listItemAligns = stylex.create({ center: { alignItems: 'center', }, start: { alignItems: 'flex-start', }, end: { alignItems: 'flex-end', },})
const listIndicator = stylex.create({ base: { marginEnd: 2, minHeight: '1lh', flexShrink: 0, display: 'inline-block', verticalAlign: 'middle', },})
export type ListVariant = keyof typeof listRootVariants
export type ListAlign = keyof typeof listItemAligns
// The root renders a real <ul>; markers sit inside (list-style-position:// inside), so UA margins/padding are reset here rather than in the recipe.const listLocal = stylex.create({ ul: { marginBlock: 0, paddingInlineStart: 0 },});
// defineSlotRecipe validates the shape at compile time: default values must// exist in the axis maps, and slot keys must be `styles` or a declared axisconst listSlotRecipe = defineSlotRecipe({ slots: { root: { styles: listRoot, variants: listRootVariants }, item: { styles: listItem, variants: listItemVariants }, indicator: { styles: listIndicator }, }, defaultVariants: { variant: "marker" },});
/** * List — recipe and component colocated in one file. `<List.Root variant>` * resolves the recipe once and items inherit it through context; each part * composes via `render` (e.g. `<List.Root render={<ol />}>…</List.Root>` * for ordered lists). The root's sparse variant map (marker only) resolves * through an explicit resolver; the indicator replaces the native marker in * the plain variant. */const { withProvider, withContext } = createSlotRecipeContext(listSlotRecipe);
export const List = { Root: withProvider( "ul", (v) => [listRoot.base, v.variant === "marker" && listRootVariants.marker, listLocal.ul], "ListRoot", ), Item: withContext("li", "item", "ListItem"), Indicator: withContext("span", "indicator", "ListIndicator"),};