Svelte Headless UI

Switch

Basic example

Switches are built using the Switch component. You can toggle your switch by clicking directly on the component or by pressing the spacebar while it’s focused.

Toggling the switch fires the change event with a negated version of the checked value.

<script>
  import { Switch } from "@rgossiaux/svelte-headlessui";

  let enabled = false;
</script>

<Switch
  checked={enabled}
  on:change={(e) => (enabled = e.detail)}
  class={enabled ? "switch switch-enabled" : "switch switch-disabled"}
>
  <span class="sr-only">Enable notifications</span>
  <span class="toggle" class:toggle-on={enabled} class:toggle-off={!enabled} />
</Switch>

<style>
  :global(.switch) {
    position: relative;
    display: inline-flex;
    align-items: center;
    border-radius: 9999px;
    height: 1.5rem;
    width: 2.75rem;
  }

  :global(.switch-enabled) {
    /* Blue */
    background-color: rgb(37 99 235);
  }

  :global(.switch-disabled) {
    /* Gray */
    background-color: rgb(229 231 235);
  }

  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
  }

  .toggle {
    display: inline-block;
    width: 1rem;
    height: 1rem;
    background-color: rgb(255 255 255);
    border-radius: 9999px;
  }

  .toggle-on {
    transform: translateX(1.1rem);
  }

  .toggle-off {
    transform: translateX(-0.25rem);
  }
</style>

Styling

See here for some general notes on styling the components in this library.

Using a custom label

By default, the Switch renders a <button> as well as whatever children you pass into it. This can make it harder to implement certain UIs, since the children will be nested within the button.

In these situations, you can use the SwitchGroup component for more flexibility.

This example demonstrates how to use the SwitchGroup, Switch, and SwitchLabel components to render a label as a sibling to a button. Note that SwitchLabel is used alongside a Switch, and both must be rendered within a parent SwitchGroup.

<script>
  import {
    Switch,
    SwitchLabel,
    SwitchGroup,
  } from "@rgossiaux/svelte-headlessui";

  let enabled = false;
</script>

<SwitchGroup>
  <div class="switch-container">
    <SwitchLabel class="switch-label">Enable notifications</SwitchLabel>
    <Switch
      checked={enabled}
      on:change={(e) => (enabled = e.detail)}
      class={enabled ? "switch switch-enabled" : "switch switch-disabled"}
    >
      <span class="sr-only">Enable notifications</span>
      <span
        class="toggle"
        class:toggle-on={enabled}
        class:toggle-off={!enabled}
      />
    </Switch>
  </div>
</SwitchGroup>

<style>
  .switch-container {
    display: flex;
    align-items: center;
  }

  :global(.switch-label) {
    margin-right: 1rem;
  }

  :global(.switch) {
    position: relative;
    display: inline-flex;
    align-items: center;
    border-radius: 9999px;
    height: 1.5rem;
    width: 2.75rem;
    transition-property: color, background-color, border-color,
      text-decoration-color, fill, stroke;
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
    transition-duration: 150ms;
  }

  :global(.switch:focus) {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 2px rgb(99 102 241);
  }

  :global(.switch-enabled) {
    /* Blue */
    background-color: rgb(37 99 235);
  }

  :global(.switch-disabled) {
    /* Gray */
    background-color: rgb(229 231 235);
  }

  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
  }

  .toggle {
    display: inline-block;
    width: 1rem;
    height: 1rem;
    background-color: rgb(255 255 255);
    border-radius: 9999px;
    transition-property: transform;
  }

  .toggle-on {
    transform: translateX(1.1rem);
  }

  .toggle-off {
    transform: translateX(-0.25rem);
  }
</style>

By default, clicking a SwitchLabel will toggle the switch, just like labels in native HTML checkboxes do. If you’d like to make the label non-clickable (which you might if it doesn’t make sense for your design), you can add a passive prop to the SwitchLabel component:

<script>
  import {
    Switch,
    SwitchLabel,
    SwitchGroup,
  } from "@rgossiaux/svelte-headlessui";

  let enabled = false;
</script>

<SwitchGroup>
  <SwitchLabel passive>Enable notifications</SwitchLabel>
  <Switch checked={enabled} on:change={(e) => (enabled = e.detail)}>
    <!-- ... -->
  </Switch>
</SwitchGroup>

Transitions

Because switches are always rendered to the DOM (rather than being mounted/unmounted like other components in the library), there’s generally no need to use the provided Transition component. You can just use CSS transitions or Svelte’s transition directives:

<script>
  import {
    Switch,
    SwitchLabel,
    SwitchGroup,
  } from "@rgossiaux/svelte-headlessui";

  let enabled = false;
</script>

<Switch checked={enabled} on:change={(e) => (enabled = e.detail)}>
  <span class="toggle" class:enabled>
    <!-- ... -->
  </span></Switch
>

<style>
  .toggle {
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
    transition-duration: 150ms;
    transition-property: transform;
  }

  .enabled {
    transform: translateX(1rem);
  }
</style>

Using with HTML forms

If you add the name prop to your switch, a hidden input element will be rendered and kept in sync with the switch state.

<script>
  import {
    Switch,
    SwitchLabel,
    SwitchGroup,
  } from "@rgossiaux/svelte-headlessui";

  let enabled = false;
</script>

<Switch checked={enabled} on:change={(e) => (enabled = e.detail)} name="notifications">  
    <!-- ... -->
</Switch>

This lets you use a radio group inside a native HTML <form> and make traditional form submissions as if your radio group was a native HTML form control.

By default, the value will be 'on' when the switch is checked, and not present when the switch is unchecked.

<input type="hidden" name="notifications" value="on" />

You can customize the value if needed by using the value prop:

<script>
  import {
    Switch,
    SwitchLabel,
    SwitchGroup,
  } from "@rgossiaux/svelte-headlessui";

  let enabled = false;
</script>

<Switch checked={enabled} on:change={(e) => (enabled = e.detail)} name="terms" value="accept">  
    <!-- ... -->
</Switch>

The hidden input will then use your custom value when the switch is checked:

<input type="hidden" name="terms" value="accept" />

Accessibility notes

Labels

By default, the children of a Switch will be used as the label for screen readers. If you’re using SwitchLabel, the content of your Switch component will be ignored by assistive technologies.

Mouse interaction

Clicking a Switch or a SwitchLabel toggles the switch on and off.

Keyboard interaction

When the horizontal prop is set, the <ArrowUp> and <ArrowDown> below become <ArrowLeft> and <ArrowRight>:

Command Description
<Space> when a Switch is focused Toggles the switch

Other

All relevant ARIA attributes are automatically managed.

For a full reference on all accessibility features implemented in Switch, see the ARIA spec on Switch.

Component API

Switch

The main switch component.

Prop Default Type Description
as button string The element the Switch should render as
checked false boolean Whether the switch is checked
name string The name used when using this component inside a form.
value string The value used when using this component inside a form, if it is checked.
Slot prop Type Description
checked boolean Whether the switch is checked

This component also dispatches a custom event, which is listened to using the Svelte on: directive:

Event name Type of event .detail Description
change T Dispatched when a Switch is toggled; the event detail contains the new value of the switch

SwitchLabel

A label that can be used for more control over the text your switch will announce to screenreaders. Renders an element that is linked to the Switch via the aria-labelledby attribute and an autogenerated id.

Prop Default Type Description
as label string The element the SwitchLabel should render as

SwitchDescription

This is the description for your switch. When this is used, it will set the aria-describedby on the switch.

Prop Default Type Description
as p string The element the SwitchDescription should render as
Slot prop Type Description
open boolean Whether or not the switch is open

SwitchGroup

Used to wrap a Switch together with a SwitchLabel and/or SwitchDescription

Prop Default Type Description
as div string The element the SwitchGroup should render as