# Takeoff Spar — Full Documentation > Complete docs corpus for `@takeoff-ui/react-spar`, generated for LLMs. Import all components from `@takeoff-ui/react-spar`. --- # Installation Three steps. No config file. No build tool wiring beyond a CSS import. ## 1. Install the packages `@takeoff-design/tokens` is a **peer dependency** of `@takeoff-ui/react-spar`, so you install both side by side. This keeps a single tokens copy in your tree and lets you upgrade tokens independently of the React wrapper. ```bash # pnpm pnpm add @takeoff-ui/react-spar @takeoff-design/tokens # npm npm install @takeoff-ui/react-spar @takeoff-design/tokens # yarn yarn add @takeoff-ui/react-spar @takeoff-design/tokens # bun bun add @takeoff-ui/react-spar @takeoff-design/tokens ``` `@turkish-technology/spar` is still a regular dependency of `@takeoff-ui/react-spar` and is installed automatically. Add it to your own `package.json` only if your app imports from it directly (for example, when using Spar primitives that `react-spar` does not re-export). **React version:** `@takeoff-ui/react-spar` targets React 19 only. If your app is on React 18, you'll get a runtime error; pin React 19 before installing. ## 2. Import the token stylesheet Add this once to your app entry — `main.tsx`, `app/layout.tsx`, wherever your global CSS lives. ```ts import '@takeoff-design/tokens/css/default/theme.css'; ``` The tokens file is ~20KB gzipped and provides every CSS custom property the components read (`--primary-base`, `--radius-m-base`, `--effect-1-default-base`, and so on). ## 3. Wrap your tree in the provider ```tsx import { TakeoffSparProvider } from '@takeoff-ui/react-spar'; export function App({ children }) { return ( {children} ); } ``` The provider renders no DOM element. It writes `data-theme` and the optional `lang` value to `document.documentElement`, then distributes theme-level component defaults through React context. ## That's it Import any component and it just works: ```tsx import { Button } from '@takeoff-ui/react-spar'; ; ``` No Tailwind preset, no Sass config, no Webpack loader. Tokens are plain CSS; components are plain React. ## Framework-specific notes - **Next.js App Router** — put `TakeoffSparProvider` in your root `app/layout.tsx`. The token CSS can import from `app/globals.css`. - **Vite** — import the token CSS anywhere in your entry; Vite handles CSS ordering. - **Remix** — wrap `root.tsx`'s `` in `TakeoffSparProvider`; link the token CSS via `links()`. Visual customization is handled entirely through the Takeoff design tokens imported above — override CSS custom properties at the app root to rebrand. ## One more step, if you use an AI assistant Copilot, Cursor, and Claude don't know this library and will invent props unless you tell them about it. Copying one file into your repository root fixes that: ```bash cp node_modules/@takeoff-ui/react-spar/agents/AGENTS.template.md ./AGENTS.md ``` See [Using AI assistants](./ai-assistants) for tool-specific paths and how to give an assistant the full component API. Continue with [Theming & tokens](./foundations/theming) for color mode, branded theme bundles, and shared component defaults. --- # Using AI assistants Coding assistants don't know this library. Left alone they invent props, reach for a raw `
` instead of a component, or copy patterns from a different design system. One file fixes that. Find your tool below — each one is the complete setup. ### GitHub Copilot Copilot reads `.github/copilot-instructions.md` automatically, for everyone on the repository — no per-developer setup. ```bash mkdir -p .github cp node_modules/@takeoff-ui/react-spar/agents/AGENTS.template.md \ .github/copilot-instructions.md ``` Commit it. That's the whole setup. Copilot's agent mode also reads `AGENTS.md` at the repository root, so if you already have one you can point at it instead of duplicating the content. Inline completions read neither — they only see the open file. **Check it worked:** open Copilot Chat and ask it to _"add a primary button that shows a loading spinner"_. You should get `Button` from `@takeoff-ui/react-spar` with a `loading` prop — not a hand-rolled ` ); } render(); ``` ## Variants ```tsx function VariantsDemo() { const variants = ['success', 'warning', 'info', 'danger', 'neutral']; return (
{variants.map((variant) => ( {variant} Contextual feedback message. ))}
); } render(); ``` ## Appearances ```tsx function AppearancesDemo() { const appearances = ['filled', 'filledLight', 'outlined', 'gradient']; return (
{appearances.map((appearance) => ( {appearance} Visual appearance preview. ))}
); } render(); ``` ## Actions ```tsx function ActionsDemo() { return ( Payment requires attention Update your billing method to avoid service interruption. ); } render(); ``` ## Close ```tsx function CloseDemo() { const [visible, setVisible] = React.useState(true); return (
{visible ? ( setVisible(false)}> Dismissible alert Closing the alert calls onClose; the parent owns visibility. ) : ( )}
); } render(); ``` ## API Reference ### Alert {#alert} #### Props {#alert-props} | Name | Type | Default | Description | | ------------------------------- | ----------------------------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------- | | children | `React.ReactNode` | - | Alert content, usually Alert.Content plus optional Alert.Actions and Alert.Close. | | variant | `AlertVariant` | 'neutral' | Defines the visual variant of the alert. | | appearance | `AlertAppearance` | 'filled' | Visual appearance of the alert. | | classNames | `Partial>` | - | Per-slot extra classes. | | slotProps | `Partial>>` | - | Per-slot HTML-attribute overrides. | | className | `string` | - | Appends custom classes to the root slot of this part. | #### Events {#alert-events} | Name | Type | Default | Description | | ---------------------------- | -------------------------- | ------- | ------------------------------------- | | onClose | `() => void` | - | Called when `Alert.Close` is clicked. | #### Data attributes {#alert-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ----------------------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | | data-variant | Always | Reflects the resolved `variant` prop for theme recipe scoping. | | data-type | Always | Reflects the resolved `appearance` prop for theme recipe scoping. | ### Alert.Content {#alert-content} #### Data attributes {#alert-content-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ----------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | ### Alert.Title {#alert-title} #### Props {#alert-title-props} | Name | Type | Default | Description | | ------------------------------- | ----------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------ | | level | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | 5 | Semantic heading level used when `as` is not provided. | | classNames | `Partial>` | - | Per-slot extra classes. | | slotProps | `Partial>>` | - | Per-slot HTML-attribute overrides. | #### Data attributes {#alert-title-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ----------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | ### Alert.Description {#alert-description} #### Data attributes {#alert-description-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ----------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | ### Alert.Actions {#alert-actions} #### Data attributes {#alert-actions-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ----------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | ### Alert.Close {#alert-close} #### Data attributes {#alert-close-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ----------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | ### Type Definitions {#alert-type-definitions} | Name | Definition | | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | AlertVariant | `'success' \| 'warning' \| 'info' \| 'danger' \| 'neutral'` | | AlertAppearance | `'filled' \| 'filledLight' \| 'outlined' \| 'gradient'` | ## When to use Inline, page-level feedback (gate changes, billing warnings, status notices). Not for transient stacked notifications — for those use a toast/notification component instead. ## Accessibility - The root defaults to `role="status"`, a polite live region suitable for most informational messages. Use `role="alert"` only for urgent content inserted dynamically that must interrupt assistive technology. - `Alert.Title` renders a semantic heading; choose `level` to fit the surrounding document outline. - The icon-only default `Alert.Close` receives the accessible name `"Close"`. Localize it with `aria-label`; custom close children must provide their own accessible name. - `Alert.Close` does not remove the alert. The parent must update visibility in `onClose`. --- # Badge `Badge` is a small visual label used to highlight status, count, or category. It is a standalone component (no upstream Spar primitive) that follows Takeoff visual vocabulary. ## Usage ```tsx import { Badge } from '@takeoff-ui/react-spar'; ``` ```tsx Active ``` ## Playground ```tsx function PlaygroundDemo() { return (
Primary Active 3 Info Draft
); } render(); ``` ## Variants ```tsx function VariantsDemo() { return (
Primary Secondary Neutral Info Success Danger Warning Verified Purple Cyan Business Teal White Dark
); } render(); ``` ## Appearances ```tsx function AppearancesDemo() { return (
Filled Filled Light Outlined Text
); } render(); ``` ## Sizes ```tsx function SizesDemo() { return (
Small Base Large
); } render(); ``` ## Rounded ```tsx function TkDemo() { return (
12 99+ New
); } render(); ``` ## Icons ```tsx function IconsDemo() { return (
}>Verified }>Online }>Offline
); } render(); ``` ## Dot ```tsx function DotDemo() { return (
); } render(); ``` ## Float ```tsx function FloatDemo() { return (
3
99
); } render(); ``` ## API Reference ### Badge {#badge} #### Props {#badge-props} | Name | Type | Default | Description | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------- | --------------------------------------------------------------------------------------------------------------------- | | children | `React.ReactNode` | - | Badge content. | | variant | `BadgeVariant` | 'primary' | Color variant. | | appearance | `BadgeAppearance` | 'filled' | Visual appearance. | | size | `BadgeSize` | 'base' | Size scale. | | rounded | `boolean` | false | Renders a pill-shaped badge with fully rounded corners. | | dot | `boolean` | false | Renders a minimal colored dot (8×8px) with no content. When true, children, startContent, and endContent are ignored. | | startContent | `React.ReactNode` | - | Content rendered before children — typically an icon. | | endContent | `React.ReactNode` | - | Content rendered after children. | | classNames | `Partial>` | - | Per-slot extra classes. | | slotProps | `Partial>>` | - | Per-slot HTML-attribute overrides. | | className | `string` | - | Appends custom classes to the root slot. | #### Data attributes {#badge-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ---------------------- | ----------------------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | | data-variant | Always | Reflects the resolved `variant` prop for theme recipe scoping. | | data-type | Always | Reflects the resolved `appearance` prop for theme recipe scoping. | | data-size | When `dot` is false | Reflects the resolved `size` prop for theme recipe scoping. | | data-rounded | When `rounded` is true | Styling hook for the pill-shaped state. | | data-dot | When `dot` is true | Styling hook for the minimal dot mode. | ### Type Definitions {#badge-type-definitions} | Name | Definition | | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | BadgeVariant | `'primary' \| 'secondary' \| 'neutral' \| 'info' \| 'success' \| 'danger' \| 'warning' \| 'verified' \| 'purple' \| 'cyan' \| 'business' \| 'teal' \| 'white' \| 'dark'` | | BadgeAppearance | `'filled' \| 'filledLight' \| 'outlined' \| 'text'` | | BadgeSize | `'small' \| 'base' \| 'large'` | | BadgeSlot | `'root' \| 'label' \| 'icon'` | ## When to use Reach for `Badge` for inline status pills, category tags, count bubbles, notification dots, and overlay indicators on icons/avatars. Not for dismissible filter chips with actions or interactive selection — use a dedicated chip/tag component for that. ## Accessibility - Badge renders a non-interactive `` and adds no live-region role. Do not use it as a button or rely on it to announce dynamic changes. - Do not communicate status by color alone; include visible text or an accessible equivalent in the surrounding content. - A `dot` badge intentionally removes children and icon content. Treat it as decorative unless its meaning is also conveyed elsewhere. - When a changing badge must be announced, put the appropriate live-region semantics on a stable surrounding element rather than making every Badge a global `role="status"`. --- # Breadcrumb `Breadcrumb` shows the user's current location as a trail of links back through the site hierarchy, so they can jump up the path without using the browser back button. ## Usage ```tsx import { Breadcrumb } from '@takeoff-ui/react-spar'; ``` ```tsx ``` ## Playground ```tsx Home Flights Istanbul → London ``` ```css .breadcrumb-demo-stack { display: grid; gap: 12px; } .breadcrumb-demo-note { width: fit-content; border: 1px solid var(--border-light); border-radius: 999px; background: var(--static-light); color: var(--text-dark); font-size: 13px; font-weight: 600; line-height: 1; padding: 8px 12px; } ``` ## Size ```tsx
Home Account Home Account
``` ```css .breadcrumb-demo-stack { display: grid; gap: 12px; } .breadcrumb-demo-note { width: fit-content; border: 1px solid var(--border-light); border-radius: 999px; background: var(--static-light); color: var(--text-dark); font-size: 13px; font-weight: 600; line-height: 1; padding: 8px 12px; } ``` ## Variant `type="outlined"` wraps each crumb in a bordered, backgrounded chip; `type="basic"` (the default) is the bare text trail. A label-less (icon-only) chip opts into tighter padding with the `tk-breadcrumb-item-icon-only` class — `` — since the compound API has no label prop to auto-detect it. ```tsx
Home Flights Istanbul → London Home Flights Istanbul → London
``` ```css .breadcrumb-demo-stack { display: grid; gap: 12px; } .breadcrumb-demo-note { width: fit-content; border: 1px solid var(--border-light); border-radius: 999px; background: var(--static-light); color: var(--text-dark); font-size: 13px; font-weight: 600; line-height: 1; padding: 8px 12px; } ``` ## Separators `Breadcrumb.Separator` defaults to a chevron. Pass `children` to replace it with text or an icon while keeping the same `
  • ` owner node: ```tsx
    Home Account Home / Account Home | Account Home Account
    ``` ```css .breadcrumb-demo-stack { display: grid; gap: 12px; } .breadcrumb-demo-note { width: fit-content; border: 1px solid var(--border-light); border-radius: 999px; background: var(--static-light); color: var(--text-dark); font-size: 13px; font-weight: 600; line-height: 1; padding: 8px 12px; } ``` ## Icons Compose an icon before the label inside `Breadcrumb.Link` or `Breadcrumb.Page` — the recipe sizes direct `svg`/`img` children and tones them with the crumb's text color: ```tsx
    Home Flights Istanbul → London Notifications Team inbox All updates
    ``` ```css .breadcrumb-demo-stack { display: grid; gap: 12px; } .breadcrumb-demo-note { width: fit-content; border: 1px solid var(--border-light); border-radius: 999px; background: var(--static-light); color: var(--text-dark); font-size: 13px; font-weight: 600; line-height: 1; padding: 8px 12px; } ``` ## External Link ```tsx Home Help center Refunds ``` ```css .breadcrumb-demo-stack { display: grid; gap: 12px; } .breadcrumb-demo-note { width: fit-content; border: 1px solid var(--border-light); border-radius: 999px; background: var(--static-light); color: var(--text-dark); font-size: 13px; font-weight: 600; line-height: 1; padding: 8px 12px; } ``` ## Routing Integration `onNavigate` runs for every `Breadcrumb.Link` activation (click, Enter, or Space). Spar prevents the native navigation before invoking it, so hand the destination straight to your client-side router — no `event.preventDefault()` needed. A link-level `onPress` takes priority and short-circuits `onNavigate` when both are set. ```tsx function NavigateBreadcrumbDemo() { const [last, setLast] = useState(null); const handleNavigate = (href) => { setLast(href); }; return (
    {last ? 'Navigate to ' + last : 'Click a link to intercept routing'}
    Home Booking Passenger details
    ); } render(); ``` ```css .breadcrumb-demo-stack { display: grid; gap: 12px; } .breadcrumb-demo-note { width: fit-content; border: 1px solid var(--border-light); border-radius: 999px; background: var(--static-light); color: var(--text-dark); font-size: 13px; font-weight: 600; line-height: 1; padding: 8px 12px; } ``` ### Custom link components Every part is polymorphic via `as`, so a router's link component can replace the anchor directly — no `onNavigate` interception needed: ```tsx Flights ``` `Breadcrumb.Item` also accepts a render function receiving `{ position, isCurrent, isDisabled }` for state-driven content: ```tsx {({ isDisabled }) => } ``` ## Disabled Setting `disabled` on the root cascades to every `Breadcrumb.Link`: it removes the anchor's `href`, sets `aria-disabled` on both the `
  • ); } render(); ``` ## Accessibility - `Field.Label`, `Field.Description`, and `Field.ErrorMessage` are wired to the control via stable IDs derived from the `Field` root (`aria-labelledby`, `aria-describedby`, `aria-invalid`). - `Switch` renders a focusable control with `role="switch"` and reflects its value through `aria-checked`. - When the switch has no visible label (for example a size sampler), pass `aria-label` directly on ``. - Disabled switches are removed from the tab order. Read-only switches remain focusable and do not change value. - When `name` is provided, Spar renders a synchronized hidden checkbox input for native form submission. `name` is the form field name, not the accessible label. | Key | Behavior | | ----------------------------------- | ----------------------------------- | | Enter / Space | Toggle the focused switch. | | Tab | Move focus to the next tabbable UI. | ## API Reference ### Switch {#switch} See [Spar Switch docs](https://spar.app.turkishtechlab.com/docs/Components/Switch) for primitive behavior. #### Props {#switch-props} | Name | Type | Default | Description | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------- | | children | `React.ReactNode \| ((state: SwitchRenderProps) => React.ReactNode)` | - | Compound children for switch anatomy, or a render function exposing Spar state. | | size | `SwitchSize` | 'base' | Size scale. | | variant | `SwitchVariant` | 'info' | Color variant used while checked. | | invalid | `boolean` | false | Marks the switch as visually invalid. Inherited from `` automatically; pass this prop only to override. | | classNames | `Partial>` | - | Per-slot class name overrides. | | slotProps | `Partial>>` | - | Per-slot HTML attribute overrides. | | className | `string` | - | Appends custom classes to the root slot of this part. | #### Data attributes {#switch-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------------- | ------------------- | ------------------------------------------------------------------------------- | | data-slot="root" | Always | Stable selector for the root slot. | | data-size | Always | Reflects the resolved `size` prop so theme recipes can scope size variants. | | data-variant | Always | Reflects the resolved `variant` prop so theme recipes can scope color variants. | | data-state="checked" | When checked. | Spar checked-state hook. | | data-state="unchecked" | When unchecked. | Spar unchecked-state hook. | | data-disabled | `disabled` is true. | Spar disabled-state hook. | | data-readonly | `readOnly` is true. | Spar read-only-state hook. | | data-required | `required` is true. | Spar required-state hook. | | data-invalid | `invalid` is true. | Marks invalid visual state for theme recipes. | ### Switch.Indicator {#switch-indicator} See [Spar Switch docs](https://spar.app.turkishtechlab.com/docs/Components/Switch) for primitive behavior. #### Data attributes {#switch-indicator-data-attributes} | Attribute | Applied when | Purpose | | ---------------------------------------------------- | ------------ | --------------------------------------- | | data-slot="indicator" | Always | Stable selector for the indicator slot. | | data-slot="thumb" | Always | Stable selector for the thumb slot. | ### Type Definitions {#switch-type-definitions} | Name | Definition | | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SwitchRenderProps | `{ checked: boolean; setChecked: (checked: boolean) => void; disabled: boolean; readOnly: boolean; required: boolean; invalid: boolean; isFocused: boolean; isHovered: boolean; isPressed: boolean }` | | SwitchSize | `'xlarge' \| 'large' \| 'base' \| 'small' \| 'xsmall'` | | SwitchVariant | `'info' \| 'success'` | | SwitchSlot | `'root' \| 'indicator' \| 'thumb'` | | SwitchIndicatorRenderProps | `{ checked: boolean; disabled: boolean; readOnly: boolean }` | ## When to use Any boolean toggle that applies immediately. Wrap it in `Field` to attach a label, description, or error message — field-level state (`invalid`/`disabled`/`required`/`optional`/`readOnly`) cascades into the switch. Not this — use `takeoff-checkbox` for multi-select lists or form values that commit on submit rather than instantly. --- # Checkbox `Checkbox` is a compound, accessible toggle control. The root owns visual state (`size`, plus inherited `invalid` / `disabled` / `required` / `optional` / `readOnly`), and `Checkbox.Indicator` is the bordered box that hosts the icon. Wrap `Checkbox` in a `Field` to attach a form-level label, description, or error message — the field-level state cascades into the checkbox automatically. ## Usage ```tsx import { Checkbox, Field } from '@takeoff-ui/react-spar'; ``` ```tsx ``` ## Playground ```tsx function PlaygroundDemo() { return ( Notifications We will only email you when a saved route drops in price. ); } render(); ``` ## Using with `Field` `Field` is the generic ARIA wrapper. Setting `invalid`, `disabled`, `required`, `optional`, or `readOnly` on `Field` cascades into the nested `Checkbox`, and `Field.Label`, `Field.Description`, and `Field.ErrorMessage` are wired to the field control through shared IDs. ```tsx function FieldWithCheckboxDemo() { return (
    Email me booking updates Subscribe to fare alerts We will only email you when a saved route drops in price. I accept the booking terms You must accept the terms to continue. Disabled option Read-only selection
    ); } render(); ``` ## Controlled ```tsx function ControlledCheckboxDemo() { const [accepted, setAccepted] = useState(false); return (
    Terms {accepted ? 'accepted' : 'not accepted'} I accept the booking terms
    ); } render(); ``` ## Indeterminate `indeterminate` overrides `checked` and `defaultChecked` while set, and emits `aria-checked="mixed"`. The first user toggle transitions out of the mixed state — `onChange` is always called with a plain `boolean`. In uncontrolled mode, set `indeterminate` only on the initial render and clear it from your own state after the first change. ```tsx function IndeterminateCheckboxDemo() { const [items, setItems] = useState({ seat: true, meal: false, baggage: false, }); const values = Object.values(items); const allChecked = values.every(Boolean); const noneChecked = values.every((v) => !v); const indeterminate = !allChecked && !noneChecked; const toggleAll = (next) => { setItems({ seat: next, meal: next, baggage: next }); }; return (
    All extras
    Extra legroom seat setItems({ ...items, seat: v })}> Special meal setItems({ ...items, meal: v })}> Extra baggage setItems({ ...items, baggage: v })}>
    ); } render(); ``` ## Sizes ```tsx function SizeDemo() { return (
    Small Base
    ); } render(); ``` ## Custom Icon `Checkbox.Indicator` accepts function children that receive the current `checked` and `indeterminate` state, so you can render different glyphs per state without mounting two icons. Passing a `ReactNode` instead replaces the default `icon` slot entirely. ```tsx function CustomIconDemo() { return (
    Favorite this route {({ checked }) => (checked ? : null)}
    ); } render(); ``` ## Disabled, Read-Only, and Invalid ```tsx function StateDemo() { return (
    Disabled option Disabled and selected Read-only selection I accept the booking terms You must accept the terms to continue.
    ); } render(); ``` ## Form Submission When `name` is set, Spar renders a synchronized hidden checkbox input for native form submission. Multiple checkboxes sharing the same `name` produce an array via `FormData.getAll`. ```tsx function CheckboxFormDemo() { const [saved, setSaved] = useState('Not submitted'); return (
    { event.preventDefault(); const data = new FormData(event.currentTarget); const picked = data.getAll('extras'); setSaved(picked.length ? picked.join(', ') : 'No extras'); }} > Extra legroom seat Special meal Extra baggage
    {saved}
    ); } render(); ``` ## Accessibility - `Field.Label`, `Field.Description`, and `Field.ErrorMessage` are wired to the control via stable IDs derived from the `Field` root (`aria-labelledby`, `aria-describedby`, `aria-invalid`). - `Checkbox` renders a focusable control with `role="checkbox"` and reflects its value through `aria-checked` (including `"mixed"` for the indeterminate state). - The asterisk inside `Field.Label` is decorative — `required` is also surfaced to assistive tech via the input's native `required` / `aria-required`. - Disabled checkboxes are removed from the tab order. Read-only checkboxes remain focusable and do not change value. | Key | Behavior | | ---------------- | ----------------------------------- | | Space | Toggle the focused checkbox. | | Tab | Move focus to the next tabbable UI. | ## API Reference ### Checkbox {#checkbox} See [Spar Checkbox docs](https://spar.app.turkishtechlab.com/docs/Components/Checkbox) for primitive behavior. #### Props {#checkbox-props} | Name | Type | Default | Description | | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------- | | children | `React.ReactNode \| ((state: CheckboxRenderProps) => React.ReactNode)` | - | Compound children for checkbox anatomy, or a render function exposing Spar tri-state. | | indeterminate | `boolean` | false | Indeterminate (mixed) visual + ARIA state. Overrides `checked` / `defaultChecked` and emits `aria-checked="mixed"`. | | invalid | `boolean` | false | Marks the checkbox as visually invalid. | | size | `CheckboxSize` | 'base' | Size scale. | | classNames | `Partial>` | - | Per-slot class name overrides. | | slotProps | `Partial>>` | - | Per-slot HTML attribute overrides. | | className | `string` | - | Appends custom classes to the root slot of this part. | #### Data attributes {#checkbox-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------------- | --------------------------------------------------------------------------- | | data-slot="root" | Always | Stable selector for the root slot. | | data-size | Always | Reflects the resolved `size` prop so theme recipes can scope size variants. | | data-invalid | `invalid` is true. | Marks invalid visual state for theme recipes. | | data-checked | When checked. | Spar checked-state hook. | | data-indeterminate | When indeterminate. | Spar indeterminate-state hook. | | data-disabled | `disabled` is true. | Spar disabled-state hook. | | data-readonly | `readOnly` is true. | Spar read-only-state hook. | | data-required | `required` is true. | Spar required-state hook. | ### Checkbox.Indicator {#checkbox-indicator} #### Data attributes {#checkbox-indicator-data-attributes} | Attribute | Applied when | Purpose | | ---------------------------------------------------- | ------------ | --------------------------------------- | | data-slot="indicator" | Always | Stable selector for the indicator slot. | | data-slot="icon" | Always | Stable selector for the icon slot. | ### Type Definitions {#checkbox-type-definitions} | Name | Definition | | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | CheckboxRenderProps | `{ checked: CheckedState; setChecked: (checked: CheckedState) => void; disabled: boolean; readOnly: boolean; required: boolean; invalid: boolean; isFocused: boolean; isHovered: boolean; isPressed: boolean }` | | CheckboxSize | `'small' \| 'base'` | | CheckboxSlot | `'root' \| 'indicator' \| 'icon'` | | CheckboxIndicatorRenderProps | `{ checked: boolean; indeterminate: boolean }` | ## When to use Boolean opt-in/opt-out controls, accept-terms gates, multi-select lists, and tri-state "select all" parents. Wrap in `Field` to attach a label, description, or error message that cascades state into the checkbox. Not this — use `takeoff-switch` for an on/off settings toggle and `takeoff-radio-group` for mutually-exclusive choices. --- # Drawer `Drawer` is a slide-in side panel that wraps Spar's Dialog primitive in modal mode. It provides a compound component API for building navigation menus, detail views, filters, or contextual forms that overlay the current page. ## Usage ```tsx import { Drawer } from '@takeoff-ui/react-spar'; ``` ```tsx ``` ## Playground ```tsx function PlaygroundDemo() { return ( Open Drawer Flight Details Review your selected flight information and passenger details before confirming the booking. ); } render(); ``` ## Placement The `placement` prop controls from which side the drawer slides in. ```tsx function PlacementDemo() { const [placement, setPlacement] = useState('right'); return (
    {['left', 'right', 'top', 'bottom'].map((p) => ( {p} Drawer from {p} This drawer slides in from the {p} side. ))}
    ); } render(); ``` ## Full Screen Stretch `Drawer.Panel` to the viewport with a style override to fill the whole screen. `placement` still decides which edge the panel slides in from. ```tsx function FullScreenDemo() { const fullScreen = { inset: 0, width: '100%', height: '100%', border: 'none', borderRadius: 0, }; return (
    {['right', 'bottom'].map((p) => ( Full screen from {p} Full-screen drawer The panel is stretched to the viewport by the style override and still slides in from the {p} edge. Close ))}
    ); } render(); ``` ## Footer Types `Drawer.Footer` supports a few visual variants through the `footerType` prop. Use `divided` to add a separation line or `light` to soften the footer background. ```tsx function FooterTypeDrawerDemo() { const footerTypes = ['basic', 'divided', 'light']; return (
    {footerTypes.map((type) => ( {type} {type} footer This footer uses {type} styling. ))}
    ); } render(); ``` ## Header Types `Drawer.Header` supports multiple visual variants via the `headerType` prop. Use this option to add a divider, light or dark background, or a primary-style header when the drawer content requires extra emphasis. ```tsx function HeaderTypeDrawerDemo() { const headerTypes = ['basic', 'divided', 'light', 'dark', 'primary']; return (
    {headerTypes.map((type) => ( {type} {type} header Header type: {type} ))}
    ); } render(); ``` ## Dismissible Set `dismissible={false}` to prevent the drawer from closing when the user clicks outside or presses Escape. ```tsx function DismissibleDrawerDemo() { const [open, setOpen] = useState(false); return ( Open non-dismissible Sticky Drawer This drawer can only be closed using the close control. ); } render(); ``` ## Overlay Behavior Use `blur` for a softened backdrop and `invisible` when the overlay should stay interactive but visually disappear. ```tsx function OverlayBehaviorDemo() { return (
    Blur Blurred overlay The backdrop stays visible and adds a soft blur effect behind the panel. Invisible Invisible overlay The overlay remains mounted for interaction but is visually hidden.
    ); } render(); ``` ## Intensity The `intensity` prop controls how dark the overlay appears. ```tsx function OverlayIntensityDemo() { const intensities = ['lightest', 'light', 'base', 'dark', 'darkest']; return (
    {intensities.map((intensity) => ( {intensity} {intensity} Overlay intensity: {intensity} ))}
    ); } render(); ``` ## Accessibility & Keyboard - The drawer renders as a modal `role="dialog"` with proper `aria-labelledby` and `aria-describedby` relationships. - Focus is trapped inside the panel while open and restored on close. - `Drawer.Title` provides the accessible label; `Drawer.Description` provides the accessible description. - When `dismissible={false}`, pressing Escape and clicking outside will not close the drawer. | Key | Behavior | | ------------------------------------- | ------------------------------------------------- | | Escape | Closes the drawer (unless `dismissible={false}`). | | Tab / Shift+Tab | Cycles focus within the panel. | ## Animation The drawer animates automatically via CSS transitions. The panel slides in from the configured `placement` direction and the overlay fades in. No JavaScript animation library is required. - **Panel:** `transform` 0.3s with `cubic-bezier(0.4, 0, 0.2, 1)` - **Overlay:** `opacity` 0.3s with `ease-in-out` ## API Reference ### Drawer {#drawer} #### Props {#drawer-props} | Name | Type | Default | Description | | -------------------------------- | --------------------------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | children | `React.ReactNode` | - | Drawer parts rendered inside the root. | | placement | `DrawerPlacement` | - | Side the drawer slides in from. | | dismissible | `boolean` | true | Whether the drawer can be dismissed by clicking outside or pressing Escape. | | id | `string` | - | Custom base ID for ARIA relationships. If not provided, one will be generated automatically. Sub-element IDs are derived as `${id}-title`, `${id}-description`, `${id}-content`. | | disabled | `boolean` | false | Disables all dialog triggers (prevents opening) | | open | `boolean` | - | Controlled open state | | defaultOpen | `boolean` | false | Initial open state (uncontrolled) | #### Events {#drawer-events} | Name | Type | Default | Description | | --------------------------------- | --------------------------------------- | ------- | -------------------------------- | | onOpenChange | `(open: boolean) => void` | - | Callback when open state changes | ### Drawer.Trigger {#drawer-trigger} #### Data attributes {#drawer-trigger-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ----------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | ### Drawer.Overlay {#drawer-overlay} #### Props {#drawer-overlay-props} | Name | Type | Default | Description | | ------------------------------- | ----------------------------------------------------------------------------------------- | ------------- | -------------------------------------------------------------------------- | | invisible | `boolean` | false | When true, the overlay is rendered but visually invisible. | | intensity | `DrawerOverlayIntensity` | 'base' | Overlay backdrop intensity. | | blur | `boolean` | false | Applies backdrop blur on the overlay. | | classNames | `Partial>` | - | | | slotProps | `Partial>>` | - | | | container | `HTMLElement \| null` | document.body | Portal container element. Content is portaled to document.body by default. | #### Data attributes {#drawer-overlay-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------------------ | ----------------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | | data-state | Always | `"open"` or `"closed"` — used for entry/exit animations. | | data-intensity | Always | Reflects the resolved `intensity` prop. | | data-invisible | When `invisible` is true | Indicates overlay is visually hidden but still interactive. | | data-blur | When `blur` is true | Enables backdrop blur styling on the overlay. | ### Drawer.Panel {#drawer-panel} #### Props {#drawer-panel-props} | Name | Type | Default | Description | | --------------------------------- | ----------------------------------------------------------------------------------------- | ------------- | -------------------------------------------------------------------------- | | classNames | `Partial>` | - | | | slotProps | `Partial>>` | - | | | container | `HTMLElement \| null` | document.body | Portal container element. Content is portaled to document.body by default. | | trapFocus | `boolean` | true | Enable focus trapping | | restoreFocus | `boolean` | true | Restore focus on close | | initialFocus | `HTMLElement \| (() => HTMLElement)` | - | Element to focus on open | | finalFocus | `HTMLElement \| (() => HTMLElement)` | - | Element to focus on close | #### Events {#drawer-panel-events} | Name | Type | Default | Description | | ----------------------------------------- | ---------------------------------------------- | ------- | ---------------------------------------------------------- | | onOpenAutoFocus | `(event: Event) => void` | - | Callback before auto-focus | | onCloseAutoFocus | `(event: Event) => void` | - | Callback before focus restore | | onEscapeKeyDown | `(event: KeyboardEvent) => void` | - | Escape key handler | | onPointerDownOutside | `(event: PointerEvent) => void` | - | Outside click handler | | onInteractOutside | `(event: PointerEvent) => void` | - | Outside interaction handler with preventDefault capability | #### Data attributes {#drawer-panel-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ------------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | | data-state | Always | `"open"` or `"closed"` — drives slide/scale animations. | | data-placement | Always | Reflects placement for directional CSS transitions. | ### Drawer.Header {#drawer-header} #### Props {#drawer-header-props} | Name | Type | Default | Description | | ------------------------------- | ----------------------------------------------------------------------------------------- | ------- | ------------------- | | headerType | `DrawerHeaderType` | 'basic' | Type of the header. | | classNames | `Partial>` | - | | | slotProps | `Partial>>` | - | | #### Data attributes {#drawer-header-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ----------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | | data-header-type | Always | Reflects the resolved `headerType` prop. | ### Drawer.Title {#drawer-title} #### Props {#drawer-title-props} | Name | Type | Default | Description | | ------------------------------- | ----------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | classNames | `Partial>` | - | | | slotProps | `Partial>>` | - | | | level | `number` | 5 | Semantic heading level (1-6). Sets the rendered tag (`h1`-`h6`) and `data-level` for the document outline; the visual size is fixed regardless of level. | #### Data attributes {#drawer-title-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | | data-level | Always | Reflects the resolved `level` prop for the document outline (does not change typography). | ### Drawer.Description {#drawer-description} #### Data attributes {#drawer-description-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ----------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | ### Drawer.Body {#drawer-body} #### Data attributes {#drawer-body-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ----------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | ### Drawer.Footer {#drawer-footer} #### Props {#drawer-footer-props} | Name | Type | Default | Description | | ------------------------------- | ----------------------------------------------------------------------------------------- | ------- | ------------------- | | footerType | `DrawerFooterType` | 'basic' | Type of the footer. | | classNames | `Partial>` | - | | | slotProps | `Partial>>` | - | | #### Data attributes {#drawer-footer-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ----------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | | data-footer-type | Always | Reflects the resolved `footerType` prop. | ### Drawer.Close {#drawer-close} #### Data attributes {#drawer-close-data-attributes} | Attribute | Applied when | Purpose | | ----------------------------------------------- | ------------ | ----------------------------------------------------- | | data-slot="root" | Always | Stable selector for wrapper styling on the root slot. | ### Type Definitions {#drawer-type-definitions} | Name | Definition | | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | DrawerPlacement | `'left' \| 'right' \| 'top' \| 'bottom'` | | DrawerOverlayIntensity | `'lightest' \| 'light' \| 'base' \| 'dark' \| 'darkest'` | | DrawerHeaderType | `'basic' \| 'divided' \| 'light' \| 'dark' \| 'primary'` | | DrawerFooterType | `'basic' \| 'divided' \| 'light'` | ## When to use Reach for Drawer for content that slides in from an edge and overlays the page. Not this — use `takeoff-dialog`/modal for centered focus dialogs, and a plain side `