All components
Checkbox Card
Selectable cards with label, description, and checkmark indicator in surface, subtle, outline, and solid variants.
123456789101112131415161718192021import { CheckboxCard } from "#components/ui/ark-ui/checkbox-card.tsx";import { indigoPalette } from "@stylexblocks/ui/themes/palettes.ts";
export function Example() { return ( // Root is an Ark checkbox styled as a card; it mirrors the checked state // so every part can style itself from it <CheckboxCard.Root defaultChecked variant="outline" size="md" palette={indigoPalette}> <CheckboxCard.Control> <CheckboxCard.Content> <CheckboxCard.Label>Analytics</CheckboxCard.Label> <CheckboxCard.Description>Track page views</CheckboxCard.Description> </CheckboxCard.Content> <CheckboxCard.Indicator /> </CheckboxCard.Control> <CheckboxCard.HiddenInput /> </CheckboxCard.Root> );}
// controlled: <CheckboxCard.Root checked={on} onCheckedChange={(d) => setOn(d.checked === true)}>Variants
surface
subtle
outline
solid
Sizes
sm
md
lg
Install
1npx stylexblocks add checkbox-cardSource
components/ui/ark-ui/checkbox-card.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544import * as stylex from "@stylexjs/stylex";import { useState } from "react";import { Checkbox as ArkCheckbox, useCheckboxContext } from "@ark-ui/react/checkbox";import { createSlotRecipeContext } from "#lib/ark-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe, fromMap, resolveSlotStyles, type SlotRecipeVariants,} from "#lib/recipe-props.ts";import { colorPalette } from '@stylexblocks/ui/color-palette.stylex.ts'import { colors } from '@stylexblocks/ui/tokens/colors.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 checkboxCardRoot = stylex.create({ base: { display: 'flex', flexDirection: 'column', userSelect: 'none', position: 'relative', borderRadius: semanticRadii.panel, flex: 1, outlineWidth: { default: null, ':focus-visible': '1px', }, outlineOffset: { default: null, ':focus-visible': '2px', }, outlineStyle: { default: null, ':focus-visible': 'solid', }, outlineColor: { default: null, ':focus-visible': colorPalette.focusRing, ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderError, }, opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.8, }, borderColor: { default: null, ':is(:disabled, [disabled], [data-disabled])': semanticColors.borderSubtle, }, outline: { default: null, ':is([data-invalid], [aria-invalid="true"])': '2px solid', }, },})
const checkboxCardRootSizes = stylex.create({ sm: { fontSize: fontSizes.sm, lineHeight: lineHeights.sm, }, md: { fontSize: fontSizes.sm, lineHeight: lineHeights.sm, }, lg: { fontSize: fontSizes.md, lineHeight: lineHeights.md, },})
const checkboxCardRootVariants = stylex.create({ surface: { borderWidth: '1px', borderColor: { default: semanticColors.border, ':is(:disabled, [disabled], [data-disabled])': semanticColors.borderSubtle, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.muted, }, backgroundColor: { default: null, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.subtle, ':is(:disabled, [disabled], [data-disabled])': semanticColors.bgMuted, }, color: { default: null, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.fg, }, }, subtle: { backgroundColor: semanticColors.bgMuted, }, outline: { borderWidth: '1px', borderColor: { default: semanticColors.border, ':is(:disabled, [disabled], [data-disabled])': semanticColors.borderSubtle, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.solid, }, boxShadow: { default: null, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': '0 0 0 1px var(--shadow-color)', }, '--shadow-color': { default: null, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.solid, }, }, solid: { borderWidth: '1px', backgroundColor: { default: null, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.solid, }, color: { default: null, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.contrast, }, borderColor: { default: null, ':is(:disabled, [disabled], [data-disabled])': semanticColors.borderSubtle, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.solid, }, },})
const checkboxCardRootJustifys = stylex.create({ start: { '--checkbox-card-justify': 'flex-start', }, end: { '--checkbox-card-justify': 'flex-end', }, center: { '--checkbox-card-justify': 'center', },})
const checkboxCardRootAligns = stylex.create({ start: { '--checkbox-card-align': 'flex-start', }, end: { '--checkbox-card-align': 'flex-end', }, center: { '--checkbox-card-align': 'center', },})
const checkboxCardControl = stylex.create({ base: { display: 'inline-flex', flex: 1, position: 'relative', borderRadius: semanticRadii.panel, justifyContent: 'var(--checkbox-card-justify)', alignItems: 'var(--checkbox-card-align)', },})
const checkboxCardControlSizes = stylex.create({ sm: { padding: spacing._3, gap: spacing._1_5, }, md: { padding: spacing._4, gap: spacing._2_5, }, lg: { padding: spacing._4, gap: spacing._3_5, },})
const checkboxCardControlVariants = stylex.create({ subtle: { backgroundColor: { default: null, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.muted, }, color: { default: null, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.fg, }, },})
const checkboxCardControlOrientations = stylex.create({ vertical: { flexDirection: 'column', }, horizontal: { flexDirection: 'row', },})
const checkboxCardLabel = stylex.create({ base: { fontWeight: fontWeights.medium, display: 'flex', alignItems: 'center', gap: spacing._2, opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, },})
const checkboxCardDescription = stylex.create({ base: { opacity: 0.64, fontSize: fontSizes.sm, lineHeight: lineHeights.sm, },})
const checkboxCardAddon = stylex.create({ base: { opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, },})
const checkboxCardAddonSizes = stylex.create({ sm: { paddingInline: spacing._3, paddingBlock: spacing._1_5, borderTopWidth: '1px', }, md: { paddingInline: spacing._4, paddingBlock: spacing._2, borderTopWidth: '1px', }, lg: { paddingInline: spacing._4, paddingBlock: spacing._2, borderTopWidth: '1px', },})
const checkboxCardIndicator = stylex.create({ base: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, color: colors.white, borderWidth: '1px', borderColor: { default: 'transparent', ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderError, }, borderRadius: semanticRadii.control, outlineWidth: { default: null, ':focus-visible': '1px', }, outlineOffset: { default: null, ':focus-visible': '2px', }, outlineStyle: { default: null, ':focus-visible': 'solid', }, outlineColor: { default: null, ':focus-visible': colorPalette.focusRing, }, opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, },})
const checkboxCardIndicatorSizes = stylex.create({ sm: { width: sizes._3_5, height: sizes._3_5, }, md: { width: sizes._4, height: sizes._4, padding: spacing._0_5, }, lg: { width: sizes._5, height: sizes._5, padding: spacing._0_5, },})
const checkboxCardIndicatorVariants = stylex.create({ surface: { borderColor: { default: semanticColors.borderEmphasized, ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderError, }, }, subtle: {}, outline: { borderColor: { default: semanticColors.borderEmphasized, ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderError, }, }, solid: { borderColor: { default: semanticColors.border, ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderError, }, color: colorPalette.fg, },})
const checkboxCardContent = stylex.create({ base: { display: 'flex', flexDirection: 'column', flex: 1, gap: spacing._1, justifyContent: 'var(--checkbox-card-justify)', alignItems: 'var(--checkbox-card-align)', },})
const checkboxCardContentAligns = stylex.create({ start: { textAlign: 'start', }, end: { textAlign: 'end', }, center: { textAlign: 'center', },})
export type CheckboxCardSize = keyof typeof checkboxCardRootSizes
export type CheckboxCardVariant = keyof typeof checkboxCardRootVariants
export type CheckboxCardJustify = keyof typeof checkboxCardRootJustifys
export type CheckboxCardAlign = keyof typeof checkboxCardRootAligns
export type CheckboxCardOrientation = keyof typeof checkboxCardControlOrientations
const checkboxCardRecipe = defineSlotRecipe({ slots: { root: { styles: checkboxCardRoot, sizes: checkboxCardRootSizes, variants: checkboxCardRootVariants, justifys: checkboxCardRootJustifys, aligns: checkboxCardRootAligns, }, control: { styles: checkboxCardControl, sizes: checkboxCardControlSizes, variants: checkboxCardControlVariants, orientations: checkboxCardControlOrientations, }, label: { styles: checkboxCardLabel }, description: { styles: checkboxCardDescription }, indicator: { styles: checkboxCardIndicator, sizes: checkboxCardIndicatorSizes, variants: checkboxCardIndicatorVariants, }, content: { styles: checkboxCardContent, aligns: checkboxCardContentAligns }, }, defaultVariants: { size: "md", variant: "outline", align: "start", justify: "start", orientation: "horizontal", },});
const cardChecked = stylex.create({ surface: { backgroundColor: { default: null, ":is([data-checked])": colorPalette.subtle }, color: { default: null, ":is([data-checked])": colorPalette.fg }, borderColor: { default: null, ":is([data-checked])": colorPalette.muted }, }, subtle: { backgroundColor: { default: null, ":is([data-checked])": colorPalette.muted }, color: { default: null, ":is([data-checked])": colorPalette.fg }, }, outline: { boxShadow: { default: null, ":is([data-checked])": `0 0 0 1px ${colorPalette.solid}` }, borderColor: { default: null, ":is([data-checked])": colorPalette.solid }, }, solid: { backgroundColor: { default: null, ":is([data-checked])": colorPalette.solid }, color: { default: null, ":is([data-checked])": colorPalette.contrast }, borderColor: { default: null, ":is([data-checked])": colorPalette.solid }, },});
const cardIndicatorChecked = stylex.create({ surface: { backgroundColor: { default: null, ":is([data-checked])": colorPalette.solid }, color: { default: null, ":is([data-checked])": colorPalette.contrast }, borderColor: { default: null, ":is([data-checked])": colorPalette.solid }, }, subtle: { color: { default: null, ":is([data-checked])": colorPalette.fg }, borderColor: { default: null, ":is([data-checked])": colorPalette.solid }, }, outline: { backgroundColor: { default: null, ":is([data-checked])": colorPalette.solid }, color: { default: null, ":is([data-checked])": colorPalette.contrast }, borderColor: { default: null, ":is([data-checked])": colorPalette.solid }, }, solid: { color: { default: null, ":is([data-checked])": colorPalette.solid }, borderColor: { default: null, ":is([data-checked])": "currentcolor" }, },});
const chrome = stylex.create({ cursorPointer: { cursor: "pointer" }, iconFill: { width: "100%", height: "100%" },});
function CheckIcon() { return ( <svg viewBox="0 0 12 12" fill="none" aria-hidden="true" {...stylex.props(chrome.iconFill)}> <path d="M2.5 6.4 5 8.9l4.5-5.4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> </svg> );}
function RootNode({ checked, defaultChecked, onCheckedChange, ...props}: React.ComponentProps<typeof ArkCheckbox.Root>) { const [inner, setInner] = useState(defaultChecked === true); const isChecked = checked !== undefined ? checked === true : inner; return ( <ArkCheckbox.Root {...props} checked={checked ?? inner} onCheckedChange={(details) => { setInner(details.checked === true); onCheckedChange?.(details); }} data-checked={isChecked ? "" : undefined} /> );}
function IndicatorNode({ children, ...props}: React.ComponentProps<typeof ArkCheckbox.Control>) { const checkbox = useCheckboxContext(); return ( <ArkCheckbox.Control {...props} data-checked={checkbox.checked ? "" : undefined}> {children ?? (checkbox.checked ? <CheckIcon /> : null)} </ArkCheckbox.Control> );}
const { withProvider, withContext } = createSlotRecipeContext(checkboxCardRecipe);
export type CheckboxCardVariantProps = Partial<SlotRecipeVariants<typeof checkboxCardRecipe>>;
/** * Checkbox Card — Ark UI's checkbox machine styled as a selectable card. * `<CheckboxCard.Root variant size>` resolves the recipe once; every part * inherits it through context. Root and Indicator stamp `data-checked` from * mirrored / machine state so checked chrome can target `:is([data-checked])`. */export const CheckboxCard = { Root: withProvider( RootNode, (v) => [ ...resolveSlotStyles(checkboxCardRecipe.slots.root, v), fromMap(cardChecked, v.variant), chrome.cursorPointer, ], "CheckboxCardRoot", ), Control: withContext("span", "control", "CheckboxCardControl"), Content: withContext("span", "content", "CheckboxCardContent"), Label: withContext(ArkCheckbox.Label, "label", "CheckboxCardLabel"), Description: withContext("span", "description", "CheckboxCardDescription"), Indicator: withContext( IndicatorNode, (v) => [ ...resolveSlotStyles(checkboxCardRecipe.slots.indicator, v), fromMap(cardIndicatorChecked, v.variant), ], "CheckboxCardIndicator", ), HiddenInput: ArkCheckbox.HiddenInput,};
export type CheckboxCardRootProps = React.ComponentProps<typeof CheckboxCard.Root>;export type CheckboxCardControlProps = React.ComponentProps<typeof CheckboxCard.Control>;export type CheckboxCardContentProps = React.ComponentProps<typeof CheckboxCard.Content>;export type CheckboxCardLabelProps = React.ComponentProps<typeof CheckboxCard.Label>;export type CheckboxCardDescriptionProps = React.ComponentProps<typeof CheckboxCard.Description>;export type CheckboxCardIndicatorProps = React.ComponentProps<typeof CheckboxCard.Indicator>;