All components outlinesubtleenclosedsmmdlg
Accordion
Vertically stacked disclosure panels with outline, subtle, and enclosed variants.
StyleX extracts every rule to a static CSS file at build time — no runtime style injection.
Apply any palette theme to a subtree and every token-aware style updates automatically.
Semantic tokens use light-dark(), so components adapt without extra code.
123456789101112131415161718192021222324import { Accordion } from "#components/ui/ark-ui/accordion.tsx";
export function Example() { return ( // size/variant resolve once on Root; every part inherits them via // context. Keyboard navigation and expansion come from the machine; the // indicator rotates off the machine's data-state. <Accordion.Root defaultValue={["compile"]} collapsible variant="outline" size="md"> <Accordion.Item value="compile"> <Accordion.ItemTrigger> How are styles compiled? <Accordion.ItemIndicator> <ChevronIcon /> </Accordion.ItemIndicator> </Accordion.ItemTrigger> <Accordion.ItemContent> <Accordion.ItemBody> StyleX extracts every rule to static CSS at build time. </Accordion.ItemBody> </Accordion.ItemContent> </Accordion.Item> </Accordion.Root> );}Variants
StyleX extracts every rule to a static CSS file at build time — no runtime style injection.
Apply any palette theme to a subtree and every token-aware style updates automatically.
Semantic tokens use light-dark(), so components adapt without extra code.
StyleX extracts every rule to a static CSS file at build time — no runtime style injection.
Apply any palette theme to a subtree and every token-aware style updates automatically.
Semantic tokens use light-dark(), so components adapt without extra code.
StyleX extracts every rule to a static CSS file at build time — no runtime style injection.
Apply any palette theme to a subtree and every token-aware style updates automatically.
Semantic tokens use light-dark(), so components adapt without extra code.
Sizes
StyleX extracts every rule to a static CSS file at build time — no runtime style injection.
Apply any palette theme to a subtree and every token-aware style updates automatically.
Semantic tokens use light-dark(), so components adapt without extra code.
StyleX extracts every rule to a static CSS file at build time — no runtime style injection.
Apply any palette theme to a subtree and every token-aware style updates automatically.
Semantic tokens use light-dark(), so components adapt without extra code.
StyleX extracts every rule to a static CSS file at build time — no runtime style injection.
Apply any palette theme to a subtree and every token-aware style updates automatically.
Semantic tokens use light-dark(), so components adapt without extra code.
Install
1npx stylexblocks add accordionSource
components/ui/ark-ui/accordion.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264import * as stylex from "@stylexjs/stylex";import { Accordion as ArkAccordion } from "@ark-ui/react/accordion";import { createSlotRecipeContext } from "#lib/ark-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe, resolveSlotStyles } from "#lib/recipe-props.ts";import { accordionItemIndicatorIconVars } from '@stylexblocks/ui/slot-recipes/accordion.stylex.ts'
import { colorPalette } from '@stylexblocks/ui/color-palette.stylex.ts'import { durations } from '@stylexblocks/ui/tokens/durations.stylex.ts'import { fontSizes } from '@stylexblocks/ui/tokens/font-sizes.stylex.ts'import { fontWeights } from '@stylexblocks/ui/tokens/font-weights.stylex.ts'import { lineHeights } from '@stylexblocks/ui/tokens/line-heights.stylex.ts'import { semanticColors } from '@stylexblocks/ui/semantic-tokens/colors.stylex.ts'import { semanticRadii } from '@stylexblocks/ui/semantic-tokens/radii.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'import { spacing } from '@stylexblocks/ui/tokens/spacing.stylex.ts'
const accordionRoot = stylex.create({ base: { width: sizes.full, '--accordion-radius': semanticRadii.panel, },})
const accordionRootSizes = stylex.create({ sm: { '--accordion-padding-x': spacing._3, '--accordion-padding-y': spacing._2, }, md: { '--accordion-padding-x': spacing._4, '--accordion-padding-y': spacing._2, }, lg: { '--accordion-padding-x': spacing._4_5, '--accordion-padding-y': spacing._2_5, },})
const accordionRootVariants = stylex.create({ enclosed: { borderWidth: '1px', borderRadius: 'var(--accordion-radius)', overflow: 'hidden', },})
const accordionItem = stylex.create({ base: { overflowAnchor: 'none', },})
const accordionItemVariants = stylex.create({ outline: { borderBottomWidth: '1px', }, subtle: { borderRadius: 'var(--accordion-radius)', }, enclosed: {},})
const accordionItemTrigger = stylex.create({ base: { display: 'flex', alignItems: 'center', width: sizes.full, outline: { default: 'none', ':focus-visible': '2px solid', }, gap: spacing._3, fontWeight: fontWeights.medium, borderRadius: 'var(--accordion-radius)', outlineColor: { default: null, ':focus-visible': colorPalette.focusRing, }, opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, cursor: { default: null, ':is(:disabled, [disabled], [data-disabled])': 'not-allowed', }, },})
const accordionItemTriggerSizes = stylex.create({ sm: { fontSize: fontSizes.sm, lineHeight: lineHeights.sm, paddingBlock: 'var(--accordion-padding-y)', }, md: { fontSize: fontSizes.md, lineHeight: lineHeights.md, paddingBlock: 'var(--accordion-padding-y)', }, lg: { fontSize: fontSizes.lg, lineHeight: lineHeights.lg, paddingBlock: 'var(--accordion-padding-y)', },})
const accordionItemTriggerVariants = stylex.create({ subtle: { paddingInline: 'var(--accordion-padding-x)', }, enclosed: { paddingInline: 'var(--accordion-padding-x)', },})
const accordionItemBody = stylex.create({ base: { paddingTop: 'var(--accordion-padding-y)', paddingBottom: 'calc(var(--accordion-padding-y) * 2)', },})
const accordionItemContent = stylex.create({ base: { overflow: 'hidden', borderRadius: 'var(--accordion-radius)', animationName: { default: null, ':is([data-open], [data-state="open"])': 'expand-height, fade-in', ':is([data-closed], [data-state="closed"])': 'collapse-height, fade-out', }, animationDuration: { default: null, ':is([data-open], [data-state="open"])': durations.moderate, ':is([data-closed], [data-state="closed"])': durations.moderate, }, },})
const accordionItemContentSizes = stylex.create({ sm: { fontSize: fontSizes.sm, lineHeight: lineHeights.sm, }, md: { fontSize: fontSizes.md, lineHeight: lineHeights.md, }, lg: { fontSize: fontSizes.lg, lineHeight: lineHeights.lg, },})
const accordionItemContentVariants = stylex.create({ subtle: { paddingInline: 'var(--accordion-padding-x)', }, enclosed: { paddingInline: 'var(--accordion-padding-x)', },})
const accordionItemIndicator = stylex.create({ base: { marginInlineStart: 'auto', transition: 'rotate 0.2s', transformOrigin: 'center', color: semanticColors.fgSubtle, [accordionItemIndicatorIconVars.width]: '1.2em', [accordionItemIndicatorIconVars.height]: '1.2em', },})
const accordionItemIndicatorIcon = stylex.create({ base: { width: accordionItemIndicatorIconVars.width, height: accordionItemIndicatorIconVars.height, },})
export type AccordionVariant = keyof typeof accordionRootVariants
export type AccordionSize = keyof typeof accordionRootSizes
// 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 accordionRecipe = defineSlotRecipe({ slots: { root: { styles: accordionRoot, sizes: accordionRootSizes, variants: accordionRootVariants, }, item: { styles: accordionItem, variants: accordionItemVariants }, itemTrigger: { styles: accordionItemTrigger, sizes: accordionItemTriggerSizes, variants: accordionItemTriggerVariants, }, itemBody: { styles: accordionItemBody }, itemContent: { styles: accordionItemContent, sizes: accordionItemContentSizes, variants: accordionItemContentVariants, }, itemIndicator: { styles: accordionItemIndicator }, }, defaultVariants: { size: "md", variant: "outline" },});
// Local chrome the recipe leaves to the composition layer: a native-button// reset under the trigger recipe and the indicator's open rotation, keyed// off the machine's `data-state` (legacy selector-first nesting — the// property-first conditional form is silently dropped by this compiler).const chrome = stylex.create({ triggerReset: { backgroundColor: "transparent", borderStyle: "none", paddingInline: 0, paddingBlock: 0, cursor: "pointer", textAlign: "start", color: "inherit", fontFamily: "inherit", fontSize: "inherit", }, indicator: { rotate: { default: "0deg", ':is([data-state="open"])': "180deg", }, },});
const { withProvider, withContext } = createSlotRecipeContext(accordionRecipe);
/** * Accordion — Ark's accordion machine wearing the accordion slot recipe: * keyboard navigation, single/multiple expansion, and height animation hooks * all come from the machine. `<Accordion.Root size variant>` resolves the * recipe once; every part inherits it through context and composes via * `asChild`. */export const Accordion = { Root: withProvider(ArkAccordion.Root, "root", "AccordionRoot"), Item: withContext(ArkAccordion.Item, "item", "AccordionItem"), ItemTrigger: withContext( ArkAccordion.ItemTrigger, (v) => [chrome.triggerReset, ...resolveSlotStyles(accordionRecipe.slots.itemTrigger, v)], "AccordionItemTrigger", ), ItemContent: withContext(ArkAccordion.ItemContent, "itemContent", "AccordionItemContent"), ItemBody: withContext("div", "itemBody", "AccordionItemBody"), ItemIndicator: withContext( ArkAccordion.ItemIndicator, (v) => [...resolveSlotStyles(accordionRecipe.slots.itemIndicator, v), chrome.indicator], "AccordionItemIndicator", ), ItemContext: ArkAccordion.ItemContext,};