{
  "name": "Switch",
  "category": "Forms",
  "subcategory": "Specialized",
  "description": "Toggle switch for boolean settings.",
  "thumbnail": "/preview-cards/switch.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/Switch/Switch.razor"
  ],
  "dependencies": [
    "spinner"
  ],
  "packageDependencies": [],
  "cssVars": [
    "--color-background",
    "--color-destructive",
    "--color-input",
    "--color-muted",
    "--color-muted-foreground",
    "--color-primary",
    "--color-ring",
    "--radius"
  ],
  "api": {
    "name": "Switch",
    "category": "Forms",
    "subcategory": "Specialized",
    "description": "Toggle switch for boolean settings.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/Switch/Switch.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [],
    "parameters": [
      {
        "name": "FormField",
        "type": "FormField.FormFieldContext?",
        "default": null,
        "description": null,
        "isCascading": true,
        "captureUnmatched": false
      },
      {
        "name": "Required",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Invalid",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ErrorText",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "HelperText",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Label",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Name",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Checked",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "CheckedChanged",
        "type": "EventCallback<bool>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Disabled",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Loading",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "OnLabel",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "OffLabel",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "AriaLabel",
        "type": "string?",
        "default": null,
        "description": "Accessible name for the switch button, exposed via aria-label. Recommended for icon-only switches that have no visible text. If omitted but OnLabel/OffLabel are set, the current one is used automatically so screen readers announce the switch's purpose.",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Size",
        "type": "SwitchSize",
        "default": "SwitchSize.Default",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Animated",
        "type": "bool",
        "default": null,
        "description": "When true, the thumb animates with a bouncy spring cubic-bezier timing instead of the default linear transition. Default is false.",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "HapticFeedback",
        "type": "bool",
        "default": null,
        "description": "When true, triggers a short haptic vibration via navigator.vibrate() each time the switch is toggled. Silently no-ops on browsers without the Vibration API (e.g. iOS Safari). Default is false.",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Class",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "AdditionalAttributes",
        "type": "Dictionary<string, object>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": true
      }
    ],
    "events": [
      {
        "name": "CheckedChanged",
        "type": "EventCallback<bool>",
        "description": null
      }
    ],
    "enums": [
      {
        "name": "SwitchSize",
        "values": [
          "Sm",
          "Default",
          "Lg"
        ],
        "description": null
      }
    ],
    "records": [],
    "cssVars": [
      "--color-background",
      "--color-destructive",
      "--color-input",
      "--color-muted",
      "--color-muted-foreground",
      "--color-primary",
      "--color-ring",
      "--radius"
    ],
    "examples": [
      {
        "title": "Default",
        "code": "<Flex Gap=\"2\" Align=\"center\">\n    <Switch @bind-Checked=\"_checked\" />\n    <Label>Airplane Mode</Label>\n</Flex>\n\n@code {\n    private bool _checked;\n}"
      },
      {
        "title": "With Binding",
        "code": "<Switch @bind-Checked=\"_checked\" />\n<Lumeo.Text Size=\"sm\" Color=\"muted\">Dark Mode is @(_checked ? \"on\" : \"off\")</Lumeo.Text>\n\n@code {\n    private bool _checked = true;\n}"
      },
      {
        "title": "Disabled",
        "code": "<Switch Checked=\"false\" Disabled=\"true\" />\n<Switch Checked=\"true\" Disabled=\"true\" />"
      },
      {
        "title": "Settings Example",
        "code": "<Flex Align=\"center\" Justify=\"between\">\n    <Stack Gap=\"0\">\n        <Lumeo.Text Size=\"sm\" Weight=\"medium\" Leading=\"none\">Email Notifications</Lumeo.Text>\n        <Lumeo.Text Size=\"sm\" Color=\"muted\">Receive emails about account activity.</Lumeo.Text>\n    </Stack>\n    <Switch @bind-Checked=\"_emailNotif\" />\n</Flex>"
      }
    ],
    "subComponents": {},
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "switch",
  "docsUrl": "https://lumeo.nativ.sh/components/switch",
  "sourceUrl": [
    {
      "file": "UI/Switch/Switch.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Switch/Switch.razor"
    }
  ],
  "source": [
    {
      "path": "UI/Switch/Switch.razor",
      "content": "@namespace Lumeo\n\n@if (Label is not null && !InsideFormField)\n{\n    <label class=\"text-sm font-medium leading-none\">\n        @Label\n        @if (Required)\n        {\n            <span class=\"text-destructive ml-0.5\">*</span>\n        }\n    </label>\n}\n\n<div class=\"inline-flex items-center\">\n    <button type=\"button\" role=\"switch\"\n            aria-checked=\"@Checked.ToString().ToLower()\"\n            aria-label=\"@AriaLabelValue\"\n            aria-required=\"@Required.ToString().ToLower()\"\n            aria-invalid=\"@EffectiveInvalid.ToString().ToLower()\"\n            name=\"@Name\"\n            class=\"@CssClass\"\n            @onclick=\"Toggle\"\n            disabled=\"@(Disabled || Loading)\"\n            @attributes=\"AdditionalAttributes\">\n        <span class=\"@ThumbClasses\">\n            @if (Loading)\n            {\n                <Spinner Size=\"Spinner.SpinnerSize.Sm\" Class=\"@ThumbSpinnerClass\" />\n            }\n        </span>\n    </button>\n    @if (HasOnOffLabel)\n    {\n        <span class=\"text-xs text-muted-foreground ml-2\">@CurrentOnOffLabel</span>\n    }\n</div>\n\n@if (!InsideFormField)\n{\n    @if (EffectiveInvalid && ErrorText is not null)\n    {\n        <p class=\"text-xs text-destructive\">@ErrorText</p>\n    }\n    else if (HelperText is not null)\n    {\n        <p class=\"text-xs text-muted-foreground\">@HelperText</p>\n    }\n}\n\n@code {\n    [CascadingParameter] private FormField.FormFieldContext? FormField { get; set; }\n\n    [Parameter] public bool Required { get; set; }\n    [Parameter] public bool Invalid { get; set; }\n    [Parameter] public string? ErrorText { get; set; }\n    [Parameter] public string? HelperText { get; set; }\n    [Parameter] public string? Label { get; set; }\n    [Parameter] public string? Name { get; set; }\n\n    private bool EffectiveInvalid => Invalid || FormField?.HasError == true;\n    private bool InsideFormField => FormField is not null;\n\n    [Parameter] public bool Checked { get; set; }\n    [Parameter] public EventCallback<bool> CheckedChanged { get; set; }\n    [Parameter] public bool Disabled { get; set; }\n    [Parameter] public bool Loading { get; set; }\n    [Parameter] public string? OnLabel { get; set; }\n    [Parameter] public string? OffLabel { get; set; }\n    /// <summary>\n    /// Accessible name for the switch button, exposed via <c>aria-label</c>. Recommended\n    /// for icon-only switches that have no visible text. If omitted but\n    /// <see cref=\"OnLabel\"/>/<see cref=\"OffLabel\"/> are set, the current one is\n    /// used automatically so screen readers announce the switch's purpose.\n    /// </summary>\n    [Parameter] public string? AriaLabel { get; set; }\n    [Parameter] public SwitchSize Size { get; set; } = SwitchSize.Default;\n\n    /// <summary>\n    /// When true, the thumb animates with a bouncy spring cubic-bezier timing\n    /// instead of the default linear transition. Default is <c>false</c>.\n    /// </summary>\n    [Parameter] public bool Animated { get; set; }\n\n    /// <summary>\n    /// When true, triggers a short haptic vibration via <c>navigator.vibrate()</c>\n    /// each time the switch is toggled. Silently no-ops on browsers without the\n    /// Vibration API (e.g. iOS Safari). Default is <c>false</c>.\n    /// </summary>\n    [Parameter] public bool HapticFeedback { get; set; }\n\n    [Inject] private HapticsService Haptics { get; set; } = default!;\n\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private bool HasOnOffLabel => OnLabel != null || OffLabel != null;\n    private string? CurrentOnOffLabel => Checked ? OnLabel : OffLabel;\n\n    // Prefer an explicit AriaLabel, then fall back to the currently visible On/Off text, then to Label\n    // so even consumers who don't set AriaLabel get a reasonable screen-reader announcement.\n    private string? AriaLabelValue => AriaLabel ?? CurrentOnOffLabel ?? Label;\n\n    private string TrackSizeClasses => Size switch\n    {\n        SwitchSize.Sm => \"h-4 w-7\",\n        SwitchSize.Lg => \"h-6 w-11\",\n        _ => \"h-5 w-9\"\n    };\n\n    private string ThumbSizeClasses => Size switch\n    {\n        SwitchSize.Sm => \"h-3 w-3\",\n        SwitchSize.Lg => \"h-5 w-5\",\n        _ => \"h-4 w-4\"\n    };\n\n    private string ThumbTranslateClass => Size switch\n    {\n        SwitchSize.Sm => Checked ? \"translate-x-3\" : \"translate-x-0\",\n        SwitchSize.Lg => Checked ? \"translate-x-5\" : \"translate-x-0\",\n        _ => Checked ? \"translate-x-4\" : \"translate-x-0\"\n    };\n\n    private string ThumbSpinnerClass => Size switch\n    {\n        SwitchSize.Sm => \"!h-2 !w-2\",\n        SwitchSize.Lg => \"!h-3.5 !w-3.5\",\n        _ => \"!h-3 !w-3\"\n    };\n\n    private string CssClass => string.Join(\" \", new[] { \"peer inline-flex\", TrackSizeClasses, \"shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\", Checked ? \"bg-primary\" : \"bg-input\", EffectiveInvalid ? \"ring-2 ring-destructive/20 focus-visible:ring-destructive\" : null, Loading ? \"pointer-events-none opacity-70\" : null, Class }.Where(s => !string.IsNullOrWhiteSpace(s)));\n\n    private string ThumbClasses\n    {\n        get\n        {\n            var springClass = Animated ? \"lumeo-switch-spring\" : null;\n            return string.Join(\" \", new[] { \"pointer-events-none flex items-center justify-center\", ThumbSizeClasses, \"rounded-full bg-background shadow-sm ring-0 transition-transform\", ThumbTranslateClass, springClass }.Where(s => !string.IsNullOrWhiteSpace(s)));\n        }\n    }\n\n    private async Task Toggle()\n    {\n        if (!Disabled && !Loading)\n        {\n            Checked = !Checked;\n            await CheckedChanged.InvokeAsync(Checked);\n            if (HapticFeedback) await Haptics.Light();\n        }\n    }\n\n    public enum SwitchSize { Sm, Default, Lg }\n}\n"
    }
  ],
  "slots": [],
  "serviceDependencies": [],
  "cascadingDependencies": [
    {
      "name": "FormField",
      "type": "FormField.FormFieldContext?",
      "required": false
    }
  ],
  "relatedComponents": [
    {
      "name": "Spinner",
      "reason": "used internally"
    }
  ],
  "keyboardInteractions": [],
  "tests": [
    "tests/Lumeo.Tests/Components/Switch/SwitchAnimatedTests.cs",
    "tests/Lumeo.Tests/Components/Switch/SwitchCursorTests.cs",
    "tests/Lumeo.Tests/Components/Switch/SwitchHapticFeedbackTests.cs",
    "tests/Lumeo.Tests/Components/Switch/SwitchTests.cs"
  ],
  "mdSummary": "# Switch\n\nToggle switch for boolean settings.\n\n**Package:** `Lumeo` · **Category:** Forms/Specialized\n**Docs:** https://lumeo.nativ.sh/components/switch\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| Switch | FormField | `FormField.FormFieldContext?` | `` |  |\n| Switch | Required | `bool` | `` |  |\n| Switch | Invalid | `bool` | `` |  |\n| Switch | ErrorText | `string?` | `` |  |\n| Switch | HelperText | `string?` | `` |  |\n| Switch | Label | `string?` | `` |  |\n| Switch | Name | `string?` | `` |  |\n| Switch | Checked | `bool` | `` |  |\n| Switch | CheckedChanged | `EventCallback<bool>` | `` |  |\n| Switch | Disabled | `bool` | `` |  |\n| Switch | Loading | `bool` | `` |  |\n| Switch | OnLabel | `string?` | `` |  |\n| Switch | OffLabel | `string?` | `` |  |\n| Switch | AriaLabel | `string?` | `` | Accessible name for the switch button, exposed via aria-label. Recommended for icon-only switches that have no visible text. If omitted but OnLabel/OffLabel are set, the current one is used automatically so screen readers announce the switch's purpose. |\n| Switch | Size | `SwitchSize` | `SwitchSize.Default` |  |\n| Switch | Animated | `bool` | `` | When true, the thumb animates with a bouncy spring cubic-bezier timing instead of the default linear transition. Default is false. |\n| Switch | HapticFeedback | `bool` | `` | When true, triggers a short haptic vibration via navigator.vibrate() each time the switch is toggled. Silently no-ops on browsers without the Vibration API (e.g. iOS Safari). Default is false. |\n| Switch | Class | `string?` | `` |  |\n| Switch | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- (none)\n\n## Events\n- `CheckedChanged` (`EventCallback<bool>`)\n\n## Sub-components\n- (none)\n\n## Cascading values it expects\n- `FormField` (`FormField.FormFieldContext?`)\n\n## Services it injects\n- (none)\n\n## Keyboard\n—\n\n## Example\n```razor\n<Switch />\n```\n\n## CSS variables\n`--color-background`, `--color-destructive`, `--color-input`, `--color-muted`, `--color-muted-foreground`, `--color-primary`, `--color-ring`, `--radius`\n\n## Files\n- `UI/Switch/Switch.razor`\n"
}