All components
Checkbox
Binary selection controls with outline, solid, and subtle boxes and a paired label.
12345678910111213141516import { Checkbox } from "#components/ui/ark-ui/checkbox.tsx";import { indigoPalette } from "@stylexblocks/ui/themes/palettes.ts";
export function Example() { return ( // variant/size resolve once on Root; every part inherits them via context <Checkbox.Root defaultChecked variant="solid" size="md" palette={indigoPalette}> <Checkbox.Control /> <Checkbox.Label>Email notifications</Checkbox.Label> <Checkbox.HiddenInput /> </Checkbox.Root> );}
// controlled: <Checkbox.Root checked={checked} onCheckedChange={(d) => setChecked(d.checked === true)}>// custom indicator: <Checkbox.Control><Checkbox.Indicator><MinusIcon /></Checkbox.Indicator></Checkbox.Control>Variants
outline
solid
subtle
Sizes
xs
sm
md
lg
Install
1npx stylexblocks add checkboxSource
components/ui/ark-ui/checkbox.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259import * as stylex from "@stylexjs/stylex";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,} 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 checkboxRoot = stylex.create({ base: { display: 'inline-flex', gap: spacing._2, alignItems: 'center', verticalAlign: 'top', position: 'relative', },})
const checkboxRootSizes = stylex.create({ xs: { gap: spacing._1_5, }, sm: { gap: spacing._2, }, md: { gap: spacing._2_5, }, lg: { gap: spacing._3, },})
const checkboxControl = 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 checkboxControlSizes = stylex.create({ xs: { width: sizes._3, height: sizes._3, }, 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 checkboxControlVariants = stylex.create({ outline: { borderColor: { default: semanticColors.borderEmphasized, ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderError, }, }, solid: { borderColor: { default: semanticColors.borderEmphasized, ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderError, }, }, subtle: { backgroundColor: colorPalette.muted, borderColor: { default: colorPalette.emphasized, ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderError, }, },})
const checkboxLabel = stylex.create({ base: { fontWeight: fontWeights.medium, userSelect: 'none', opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, },})
const checkboxLabelSizes = stylex.create({ xs: { fontSize: fontSizes.xs, lineHeight: lineHeights.xs, }, sm: { fontSize: fontSizes.sm, lineHeight: lineHeights.sm, }, md: { fontSize: fontSizes.sm, lineHeight: lineHeights.sm, }, lg: { fontSize: fontSizes.md, lineHeight: lineHeights.md, },})
export type CheckboxSize = keyof typeof checkboxRootSizes
export type CheckboxVariant = keyof typeof checkboxControlVariants
const checkboxRecipe = defineSlotRecipe({ slots: { root: { styles: checkboxRoot, sizes: checkboxRootSizes }, control: { styles: checkboxControl, sizes: checkboxControlSizes, variants: checkboxControlVariants, }, label: { styles: checkboxLabel, sizes: checkboxLabelSizes }, }, defaultVariants: { variant: "solid", size: "md" },});
// Checked-state chrome keyed off `data-checked` that ControlNode copies// from the machine — the generated recipe does not style the checked fill.const checkboxChecked = stylex.create({ outline: { color: { default: null, ":is([data-checked])": colorPalette.fg }, borderColor: { default: null, ":is([data-checked])": colorPalette.solid }, }, solid: { backgroundColor: { default: null, ":is([data-checked])": colorPalette.solid }, borderColor: { default: null, ":is([data-checked])": colorPalette.solid }, color: { default: null, ":is([data-checked])": colorPalette.contrast }, }, subtle: { color: { default: null, ":is([data-checked])": colorPalette.fg }, },});
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 ControlNode({ children, ...props}: React.ComponentProps<typeof ArkCheckbox.Control>) { const checkbox = useCheckboxContext(); return ( <ArkCheckbox.Control {...props} data-checked={checkbox.checked ? "" : undefined}> {children ?? <Indicator />} </ArkCheckbox.Control> );}
export interface CheckboxIndicatorProps { children?: React.ReactNode;}
/** Renders only while checked — defaults to the check icon. */function Indicator({ children }: CheckboxIndicatorProps) { const checkbox = useCheckboxContext(); if (!checkbox.checked) return null; return children ?? <CheckIcon />;}
const { withProvider, withContext } = createSlotRecipeContext(checkboxRecipe);
/** * Checkbox — Ark UI's checkbox machine wearing the checkbox slot recipe. * `<Checkbox.Root variant size>` resolves the recipe once; every part * inherits it through context. Control copies `data-checked` from the * machine so checked chrome can target `:is([data-checked])`. */export const Checkbox = { Root: withProvider( ArkCheckbox.Root, (v) => [...resolveSlotStyles(checkboxRecipe.slots.root, v), chrome.cursorPointer], "CheckboxRoot", ), Control: withContext( ControlNode, (v) => [ ...resolveSlotStyles(checkboxRecipe.slots.control, v), fromMap(checkboxChecked, v.variant), ], "CheckboxControl", ), Indicator, Label: withContext(ArkCheckbox.Label, "label", "CheckboxLabel"), HiddenInput: ArkCheckbox.HiddenInput,};
export type CheckboxRootProps = React.ComponentProps<typeof Checkbox.Root>;export type CheckboxControlProps = React.ComponentProps<typeof Checkbox.Control>;export type CheckboxLabelProps = React.ComponentProps<typeof Checkbox.Label>;