317 lines
8.7 KiB
Plaintext
317 lines
8.7 KiB
Plaintext
# Switch
|
|
|
|
<script>
|
|
import Preview from "./_Preview.svelte";
|
|
</script>
|
|
|
|
<Preview url="examples/switch" code="" class="h-[180px] bg-teal-300"/>
|
|
|
|
## 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.
|
|
|
|
```svelte
|
|
<script>
|
|
import { Switch } from "@rgossiaux/svelte-headlessui";
|
|
|
|
let enabled = false;
|
|
</script>
|
|
|
|
<Switch
|
|
bind:checked={enabled}
|
|
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](general-concepts#component-styling) 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`.
|
|
|
|
```svelte
|
|
<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
|
|
bind:checked={enabled}
|
|
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:
|
|
|
|
```svelte
|
|
<script>
|
|
import {
|
|
Switch,
|
|
SwitchLabel,
|
|
SwitchGroup,
|
|
} from "@rgossiaux/svelte-headlessui";
|
|
|
|
let enabled = false;
|
|
</script>
|
|
|
|
<SwitchGroup>
|
|
<SwitchLabel passive>Enable notifications</SwitchLabel>
|
|
<Switch bind:checked={enabled}>
|
|
<!-- ... -->
|
|
</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:
|
|
|
|
```svelte
|
|
<script>
|
|
import {
|
|
Switch,
|
|
SwitchLabel,
|
|
SwitchGroup,
|
|
} from "@rgossiaux/svelte-headlessui";
|
|
|
|
let enabled = false;
|
|
</script>
|
|
|
|
<Switch bind:checked={enabled}>
|
|
<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>
|
|
```
|
|
|
|
## 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 <a href="https://www.w3.org/TR/wai-aria-practices-1.2/#switch">the ARIA spec on Switch</a>.
|
|
|
|
## 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 |
|
|
|
|
| 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 |
|