Toggle
Form element that allows users to switch between two states, such as on and off.
| Class name | Type | |
|---|---|---|
.prs-toggle |
Component | For <input> |
.prs-toggle-label |
Component | For <label> |
.prs-toggle-warning |
Modifier | Red variant |
.prs-toggle-confirmation |
Modifier | Green variant |
.prs-toggle-sm |
Modifier | Small size |
.prs-toggle_focus |
State | Manually apply visual focus |
.prs-toggle_disabled |
State | Manually apply visual disabled |
-
Default
Use the native HTML checkbox because it provides the most accurate semantics for a toggle switch with the simplest markup. It allows users to understand the state of the toggle easily, and it is natively accessible to screen readers.
<label><input type="checkbox" class="prs-toggle" aria-label="Toggle" /></label> <label><input type="checkbox" class="prs-toggle" aria-label="Toggle" checked /></label> -
With label
An example with a little help from Tailwind CSS.
<div class="w-96 max-w-xs divide-y space-y-5 [&>:where(:not(:first-child))]:pt-5"> <!-- <- this is just for demo purposes --> <label class="prs-toggle-label"> <span class="prs-label-text">Label at the start</span> <input type="checkbox" class="prs-toggle" /> </label> <label class="prs-toggle-label"> <input type="checkbox" class="prs-toggle" /> <span class="prs-label-text">Label at the end that breaks to multiple lines and still looks decent. Please try to maintain one line!</span> </label> </div> -
<label><input type="checkbox" class="prs-toggle prs-toggle-warning" aria-label="Toggle" checked /></label> -
<label><input type="checkbox" class="prs-toggle prs-toggle-confirmation" aria-label="Toggle" checked /></label> -
<label><input type="checkbox" class="prs-toggle prs-toggle-sm" aria-label="Toggle" /></label> -
Icon
Use icons to represent the toggle states. The first icon is for the "unchecked" state, and the second one is for the "checked" state.
<label class="prs-toggle"> <input type="checkbox" aria-label="Toggle" /> <span><svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" role="img"><use href="/_assets/prs-icons.svg#nav-x" /></svg></span> <span><svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" role="img"><use href="/_assets/prs-icons.svg#status-check" /></svg></span> </label> <label class="prs-toggle prs-toggle-sm"> <input type="checkbox" aria-label="Toggle" /> <span><svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" role="img"><use href="/_assets/prs-icons.svg#nav-x" /></svg></span> <span><svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" role="img"><use href="/_assets/prs-icons.svg#status-check" /></svg></span> </label> -
Indeterminate
The indeterminate state is useful for checkboxes that represent a mixed state, such as when some but not all items are selected.
<label> <input type="checkbox" class="prs-toggle" aria-label="Toggle" x-init="$el.indeterminate = true" @click="$el.indeterminate = !$el.checked" /> </label> <label> <input type="checkbox" class="prs-toggle prs-toggle-sm" aria-label="Toggle" x-init="$el.indeterminate = true" @click="$el.indeterminate = !$el.checked" /> </label> -
Focus
Focus styles will apply automatically, but we also provide
.prs-toggle_focusif you need to manually apply the focus state.<label><input type="checkbox" class="prs-toggle prs-toggle_focus" aria-label="Toggle" /></label> <label><input type="checkbox" class="prs-toggle prs-toggle_focus" aria-label="Toggle" checked /></label> <label><input type="checkbox" class="prs-toggle prs-toggle-sm prs-toggle_focus" aria-label="Toggle" /></label> <label><input type="checkbox" class="prs-toggle prs-toggle-sm prs-toggle_focus" aria-label="Toggle" checked /></label> -
<label><input type="checkbox" class="prs-toggle" aria-label="Toggle" disabled /></label> <label><input type="checkbox" class="prs-toggle" aria-label="Toggle" checked disabled /></label> <label><input type="checkbox" class="prs-toggle prs-toggle-warning" aria-label="Toggle" checked disabled /></label> <label><input type="checkbox" class="prs-toggle prs-toggle-confirmation" aria-label="Toggle" checked disabled /></label>
CSS
Full Library
Component Only
.prs-toggle {
--toggle-size: 1.75rem;
--toggle-pad: 1px;
--toggle-border: 1px;
--toggle-content: '';
--toggle-color: var(--prs-c-gray-600);
--toggle-radius-max: calc(var(--prs-radius-badge) + var(--prs-radius-badge) + var(--prs-radius-badge));
appearance: none;
padding: var(--toggle-pad);
border: var(--toggle-border) solid var(--toggle-color);
width: calc((var(--toggle-size) * 2) - (var(--toggle-border) + var(--toggle-pad)) * 3);
height: var(--toggle-size);
background-color: var(--toggle-color);
color: var(--prs-c-white);
vertical-align: middle;
display: inline-grid;
flex-shrink: 0;
grid-template-columns: 0fr 1fr 1fr;
place-content: center;
position: relative;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
border-radius: calc(var(--prs-radius-badge) + min(var(--toggle-pad), var(--toggle-radius-max)) + min(var(--toggle-border), var(--toggle-radius-max)));
box-shadow: 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000) inset;
transition: background-color 0.3s, color 0.3s, grid-template-columns 0.2s;
& > * {
appearance: none;
padding: 0;
border: none;
height: 100%;
cursor: pointer;
background-color: transparent;
color: var(--prs-c-gray-800);
display: flex;
grid-column: 2 / span 1;
grid-row-start: 1;
align-items: center;
justify-content: center;
z-index: 1;
transition: opacity 0.2s, rotate 0.4s;
&:focus-visible {
outline: 0 none;
}
&:nth-child(2) {
rotate: none;
}
&:nth-child(3) {
opacity: 0;
rotate: -15deg;
}
&:is(svg),& > :is(svg) {
width: 1em;
height: 1em;
}
}
&:has(:checked) {
& > :nth-child(2) {
opacity: 0;
rotate: 15deg;
}
& > :nth-child(3) {
opacity: 1;
rotate: none;
}
}
&:before {
height: 100%;
aspect-ratio: 1;
content: var(--toggle-content);
background-color: currentColor;
grid-row-start: 1;
grid-column-start: 2;
position: relative;
inset-inline-start: 0;
border-radius: var(--prs-radius-badge);
box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset,
0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset,
0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000);
translate: 0;
transition: background-color 0.1s, translate 0.2s, inset-inline-start 0.2s;
}
@media print {
&:before {
outline: 0.25rem solid;
outline-offset: -1rem;
}
}
&:focus-visible,
&.prs-toggle_focus,
&:has(:focus-visible),
&.prs-toggle_focus:has(:focus-visible) {
outline: 2px solid var(--prs-c-aqua);
outline-offset: 1px;
}
&:checked,
&[aria-checked="true"],
&:has(> input:checked) {
--toggle-color: var(--prs-c-aqua);
grid-template-columns: 1fr 1fr 0fr;
&:before {
background-color: currentColor;
}
@starting-style {
&:before {
opacity: 0;
}
}
}
&:indeterminate {
grid-template-columns: 0.5fr 1fr 0.5fr;
}
&:disabled,
&.prs-toggle_disabled,
&:has(> input:disabled),
&.prs-toggle_disabled:has(> input:disabled) {
cursor: not-allowed;
opacity: 0.3;
& > * {
cursor: not-allowed;
pointer-events: none;
}
}
}
.prs-toggle-warning {
&:checked,
&[aria-checked="true"] {
--toggle-color: var(--prs-c-red);
}
}
.prs-toggle-confirmation {
&:checked,
&[aria-checked="true"] {
--toggle-color: var(--prs-c-green);
}
}
.prs-toggle-sm {
font-size: 0.8125rem;
&[type='checkbox'],
&:has([type='checkbox']) {
--toggle-size: 1.125rem;
width: calc((var(--toggle-size) * 2) - (var(--toggle-border) + var(--toggle-pad)) * 2);
}
}
Figma
Coming soon...