{
  "name": "toggle",
  "layer": "base",
  "type": "component",
  "title": "Toggle field components",
  "description": "Free and open-source React toggle field components built for modern applications and websites. These toggle fields are built using React Aria and styled with Tailwind CSS.",
  "files": [
    {
      "path": "components/base/toggle/toggle.tsx",
      "target": "components/base/toggle/toggle.tsx",
      "type": "component",
      "content": "\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { Switch as AriaSwitch, type SwitchProps as AriaSwitchProps } from \"react-aria-components\";\nimport { cx, sortCx } from \"@/utils/cx\";\nimport { warnDomProps } from \"@/utils/warn-dom-props\";\n\nexport interface ToggleBaseProps {\n    /** The size of the toggle. */\n    size?: \"sm\" | \"md\";\n    /** Renders a slimmer track without the resting inner shadow. */\n    slim?: boolean;\n    /** Additional CSS classes to apply to the root element. */\n    className?: string;\n    /** Whether the toggle is currently hovered. */\n    isHovered?: boolean;\n    /** Whether the toggle should show a visible focus ring. */\n    isFocusVisible?: boolean;\n    /** Whether the toggle is selected (on). */\n    isSelected?: boolean;\n    /** Whether the toggle is disabled. */\n    isDisabled?: boolean;\n}\n\nexport const ToggleBase = ({ className, isHovered, isDisabled, isFocusVisible, isSelected, slim, size = \"sm\" }: ToggleBaseProps) => {\n    const trackStyles = sortCx({\n        default: {\n            sm: {\n                root: \"h-5 w-9 p-0.5\",\n                switch: cx(\"size-4\", isSelected && \"translate-x-4\"),\n            },\n            md: {\n                root: \"h-6 w-11 p-0.5\",\n                switch: cx(\"size-5\", isSelected && \"translate-x-5\"),\n            },\n        },\n        slim: {\n            sm: {\n                root: \"h-4 w-8\",\n                switch: cx(\"size-4\", isSelected && \"translate-x-4\"),\n            },\n            md: {\n                root: \"h-5 w-10\",\n                switch: cx(\"size-5\", isSelected && \"translate-x-5\"),\n            },\n        },\n    });\n\n    const classes = slim ? trackStyles.slim[size] : trackStyles.default[size];\n\n    return (\n        <div\n            className={cx(\n                \"bg-tertiary ring-secondary outline-focus-ring cursor-pointer rounded-full ring-[0.5px] transition duration-150 ease-linear ring-inset\",\n                isSelected && \"bg-brand-solid\",\n                isSelected && isHovered && \"bg-brand-solid_hover\",\n                isDisabled && \"cursor-not-allowed opacity-50\",\n                isFocusVisible && \"outline-2 outline-offset-2\",\n\n                slim && \"ring-1\",\n                slim && isSelected && \"ring-transparent\",\n                classes.root,\n                className,\n            )}\n        >\n            <div\n                style={{\n                    transition: \"transform 0.15s ease-in-out, translate 0.15s ease-in-out, border-color 0.1s linear, background-color 0.1s linear\",\n                }}\n                className={cx(\n                    \"bg-fg-white rounded-full shadow-sm\",\n\n                    slim && \"shadow-xs\",\n                    slim && \"border-toggle-border border\",\n                    slim && isSelected && \"border-toggle-slim-border_pressed\",\n                    slim && isSelected && isHovered && \"border-toggle-slim-border_pressed-hover\",\n\n                    classes.switch,\n                )}\n            />\n        </div>\n    );\n};\nToggleBase.displayName = \"ToggleBase\";\n\nconst styles = sortCx({\n    sm: {\n        root: \"gap-2\",\n        textWrapper: \"\",\n        label: \"text-sm font-medium\",\n        hint: \"text-sm\",\n    },\n    md: {\n        root: \"gap-3\",\n        textWrapper: \"gap-0.5\",\n        label: \"text-md font-medium\",\n        hint: \"text-md\",\n    },\n});\n\nexport interface ToggleProps extends AriaSwitchProps {\n    /** The size of the toggle. */\n    size?: \"sm\" | \"md\";\n    /** The label rendered next to the toggle. */\n    label?: string;\n    /** A supporting hint rendered below the label. */\n    hint?: ReactNode;\n    /** Renders a slimmer track without the resting inner shadow. */\n    slim?: boolean;\n    /**\n     * Which side of the switch the label (and hint) render on.\n     *\n     * @default \"end\"\n     */\n    labelPosition?: \"start\" | \"end\";\n}\n\nexport const Toggle = ({ label, hint, className, size = \"sm\", slim, labelPosition = \"end\", ...ariaSwitchProps }: ToggleProps) => {\n    warnDomProps(\"Toggle\", ariaSwitchProps as Record<string, unknown>, { checked: \"isSelected\", disabled: \"isDisabled\" });\n\n    return (\n        <AriaSwitch\n            {...ariaSwitchProps}\n            className={(state) =>\n                cx(\n                    \"relative flex w-max items-start\",\n                    labelPosition === \"start\" && \"flex-row-reverse\",\n                    state.isDisabled && \"cursor-not-allowed\",\n                    styles[size].root,\n                    typeof className === \"function\" ? className(state) : className,\n                )\n            }\n        >\n            {({ isSelected, isDisabled, isFocusVisible, isHovered }) => (\n                <>\n                    <ToggleBase\n                        slim={slim}\n                        size={size}\n                        isHovered={isHovered}\n                        isDisabled={isDisabled}\n                        isFocusVisible={isFocusVisible}\n                        isSelected={isSelected}\n                        className={slim ? \"mt-0.5\" : \"\"}\n                    />\n\n                    {(label || hint) && (\n                        <div className={cx(\"flex flex-col\", styles[size].textWrapper)}>\n                            {label && <p className={cx(\"text-secondary select-none\", styles[size].label)}>{label}</p>}\n                            {hint && (\n                                <span role=\"presentation\" className={cx(\"text-tertiary\", styles[size].hint)} onClick={(event) => event.stopPropagation()}>\n                                    {hint}\n                                </span>\n                            )}\n                        </div>\n                    )}\n                </>\n            )}\n        </AriaSwitch>\n    );\n};\nToggle.displayName = \"Toggle\";\n",
      "dependencies": [
        "react",
        "react-aria-components"
      ]
    }
  ],
  "registryDependencies": [
    "cx",
    "warn-dom-props"
  ],
  "optionalRegistryDependencies": [],
  "dependencies": [
    "react",
    "react-aria-components"
  ],
  "cssVars": [],
  "examples": [
    "base",
    "disabled",
    "sizes",
    "slim",
    "slim-with-label-and-hint",
    "toggle-example",
    "with-label",
    "with-label-and-hint"
  ],
  "docs": "/components/toggles",
  "intent": "Let a user immediately switch a single setting on or off, applied right away rather than staged for form submit.",
  "avoid_when": [
    "the change should wait for an explicit form submit (use checkbox)",
    "there are more than two states"
  ],
  "composes_with": [
    "form",
    "table",
    "notifications"
  ],
  "a11y_contract": [
    "built on React Aria Switch: `role=\"switch\"`, keyboard toggling and checked-state semantics come for free; visible children become the accessible label, so an icon-only toggle still needs an aria-label"
  ],
  "requires_data": [
    "a visible label describing the setting being toggled"
  ],
  "token_contract": [
    "bg-brand-solid",
    "bg-brand-solid_hover",
    "bg-fg-white",
    "bg-tertiary",
    "border-toggle-border",
    "border-toggle-slim-border_pressed",
    "border-toggle-slim-border_pressed-hover",
    "text-secondary",
    "text-tertiary"
  ],
  "changelog": [
    {
      "version": "0.3.0",
      "changes": [
        "Add `DataTable` and `useDataTable` (`application/data-table`): sorting with optional multi-column stacking, search, faceted, text, number-range and date-range filters, column visibility, pinning and resizing, row selection with a bulk-action bar, client-side pagination, a `manual` server-side mode driven by `onQueryChange`, density toggle, loading skeleton rows, empty state, sticky header and virtualization through React Aria's `Virtualizer` and `TableLayout`. `Table` is unchanged and remains the styling primitive."
      ]
    },
    {
      "version": "0.2.0",
      "changes": [
        "Field API and accessible-name fixes from the agent feedback map (docs/spec/feedback/2026-09-11-agent-feedback-map.md items 2.17, 2.20, 2.22, and the JSDoc half of 2.11), batched across `input`, `select`, `checkbox`, `radio-buttons`, `badges`, `toggle`, `tags`, `buttons/button`, `date-picker`, `page-headers`, and `foundations/featured-icon`:"
      ]
    }
  ],
  "platforms": [
    "react",
    "next"
  ]
}
