Switch

Binary control primitive that reflects switch semantics for checked state.

Default

With checked omitted, Switch starts off and enabled.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-switch/ui-switch.html"
>

<ui-switch>Frodo is available</ui-switch>
<script setup>
  import { UiSwitch } from "@threadlabs/looma";
</script>

<template>
  <UiSwitch>Frodo is available</UiSwitch>
</template>
import { UiSwitch } from "@threadlabs/looma";

export default function Example() {
  return <UiSwitch>Frodo is available</UiSwitch>;
}
/** @jsxImportSource preact */
import { UiSwitch } from "@threadlabs/looma";

export default function Example() {
  return <UiSwitch>Frodo is available</UiSwitch>;
}
<script>
  import { UiSwitch } from "@threadlabs/looma";
</script>

<UiSwitch>Frodo is available</UiSwitch>
import { Component } from "@angular/core";
import { UiSwitch } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiSwitch],
  template: `
    <label
      ui-switch
    >Frodo is available</label>
  `,
})
export default class Example {}

checked and disabled

Default-checked supplies initial on state while disabled prevents interaction.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-switch/ui-switch.html"
>

<ui-cluster
  gap="l"
>
  <ui-switch
    checked
  >Checked</ui-switch>
  <ui-switch
    disabled
  >Disabled</ui-switch>
</ui-cluster>
<script setup>
  import { UiCluster, UiSwitch } from "@threadlabs/looma";
</script>

<template>
  <UiCluster
    gap="l"
  >
    <UiSwitch
      :checked="true"
    >Checked</UiSwitch>
    <UiSwitch
      :disabled="true"
    >Disabled</UiSwitch>
  </UiCluster>
</template>
import { UiCluster, UiSwitch } from "@threadlabs/looma";

export default function Example() {
  return <UiCluster
    gap="l"
  >
    <UiSwitch
      checked
    >Checked</UiSwitch>
    <UiSwitch
      disabled
    >Disabled</UiSwitch>
  </UiCluster>;
}
/** @jsxImportSource preact */
import { UiCluster, UiSwitch } from "@threadlabs/looma";

export default function Example() {
  return <UiCluster
    gap="l"
  >
    <UiSwitch
      checked
    >Checked</UiSwitch>
    <UiSwitch
      disabled
    >Disabled</UiSwitch>
  </UiCluster>;
}
<script>
  import { UiCluster, UiSwitch } from "@threadlabs/looma";
</script>

<UiCluster
  gap="l"
>
  <UiSwitch
    checked
  >Checked</UiSwitch>
  <UiSwitch
    disabled
  >Disabled</UiSwitch>
</UiCluster>
import { Component } from "@angular/core";
import { UiCluster, UiSwitch } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiCluster, UiSwitch],
  template: `
    <div
      ui-cluster
      [gap]="'l'"
    >
      <label
        ui-switch
        [checked]="true"
      >Checked</label>
      <label
        ui-switch
        [disabled]="true"
      >Disabled</label>
    </div>
  `,
})
export default class Example {}

name, value, and required

Name and value are what the form submits when the switch is on (value defaults to "on"), and a switch that is off sends nothing; required blocks submission until it is switched on.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-switch/ui-switch.html"
>

<form>
  <ui-switch
    name="alerts"
    value="enabled"
    required
  >Email alerts</ui-switch>
</form>
<script setup>
  import { UiSwitch } from "@threadlabs/looma";
</script>

<template>
  <form>
    <UiSwitch
      name="alerts"
      value="enabled"
      :required="true"
    >Email alerts</UiSwitch>
  </form>
</template>
import { UiSwitch } from "@threadlabs/looma";

export default function Example() {
  return <form>
    <UiSwitch
      name="alerts"
      value="enabled"
      required
    >Email alerts</UiSwitch>
  </form>;
}
/** @jsxImportSource preact */
import { UiSwitch } from "@threadlabs/looma";

export default function Example() {
  return <form>
    <UiSwitch
      name="alerts"
      value="enabled"
      required
    >Email alerts</UiSwitch>
  </form>;
}
<script>
  import { UiSwitch } from "@threadlabs/looma";
</script>

<form>
  <UiSwitch
    name="alerts"
    value="enabled"
    required
  >Email alerts</UiSwitch>
</form>
import { Component } from "@angular/core";
import { UiSwitch } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiSwitch],
  template: `
    <form>
      <label
        ui-switch
        [name]="'alerts'"
        [value]="'enabled'"
        [required]="true"
      >Email alerts</label>
    </form>
  `,
})
export default class Example {}

With a description

The description slot says what the setting does, on a line under its label. It is the input's accessible description, not part of its name.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-switch/ui-switch.html"
>

<ui-switch
  name="reviews"
  checked
>
  Reviews
  <span
    slot="description"
  >Editors can ask someone to review their changes before they publish.</span>
</ui-switch>
<script setup>
  import { UiSwitch } from "@threadlabs/looma";
</script>

<template>
  <UiSwitch
    name="reviews"
    :checked="true"
  >Reviews<template #description>
      <span>Editors can ask someone to review their changes before they publish.</span>
    </template></UiSwitch>
</template>
import { UiSwitch } from "@threadlabs/looma";

export default function Example() {
  return <UiSwitch
    name="reviews"
    checked
    slots={{
      description: <span>Editors can ask someone to review their changes before they publish.</span>,
    }}
  >Reviews</UiSwitch>;
}
/** @jsxImportSource preact */
import { UiSwitch } from "@threadlabs/looma";

export default function Example() {
  return <UiSwitch
    name="reviews"
    checked
    slots={{
      description: <span>Editors can ask someone to review their changes before they publish.</span>,
    }}
  >Reviews</UiSwitch>;
}
<script>
  import { UiSwitch } from "@threadlabs/looma";
</script>

{#snippet description()}
  <span>Editors can ask someone to review their changes before they publish.</span>
{/snippet}

<UiSwitch
  name="reviews"
  checked
  slots={{ description }}
>Reviews</UiSwitch>
import { Component } from "@angular/core";
import { UiSwitch } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiSwitch],
  template: `
    <label
      ui-switch
      [name]="'reviews'"
      [checked]="true"
    >Reviews<ng-container ngProjectAs="[slot='description']">
        <span>Editors can ask someone to review their changes before they publish.</span>
      </ng-container></label>
  `,
})
export default class Example {}

size

size="sm" keeps the switch's track and centres its label in the small control height, in the small controls' type size, so it lines up with a small button.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-switch/ui-switch.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-button/ui-button.html"
>

<ui-cluster
  gap="s"
  align="center"
>
  <ui-switch
    size="sm"
  >Switch</ui-switch>
  <ui-button
    size="sm"
  >Small button</ui-button>
</ui-cluster>
<script setup>
  import { UiButton, UiCluster, UiSwitch } from "@threadlabs/looma";
</script>

<template>
  <UiCluster
    gap="s"
    align="center"
  >
    <UiSwitch
      size="sm"
    >Switch</UiSwitch>
    <UiButton
      size="sm"
    >Small button</UiButton>
  </UiCluster>
</template>
import { UiButton, UiCluster, UiSwitch } from "@threadlabs/looma";

export default function Example() {
  return <UiCluster
    gap="s"
    align="center"
  >
    <UiSwitch
      size="sm"
    >Switch</UiSwitch>
    <UiButton
      size="sm"
    >Small button</UiButton>
  </UiCluster>;
}
/** @jsxImportSource preact */
import { UiButton, UiCluster, UiSwitch } from "@threadlabs/looma";

export default function Example() {
  return <UiCluster
    gap="s"
    align="center"
  >
    <UiSwitch
      size="sm"
    >Switch</UiSwitch>
    <UiButton
      size="sm"
    >Small button</UiButton>
  </UiCluster>;
}
<script>
  import { UiButton, UiCluster, UiSwitch } from "@threadlabs/looma";
</script>

<UiCluster
  gap="s"
  align="center"
>
  <UiSwitch
    size="sm"
  >Switch</UiSwitch>
  <UiButton
    size="sm"
  >Small button</UiButton>
</UiCluster>
import { Component } from "@angular/core";
import { UiButton, UiCluster, UiSwitch } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiCluster, UiSwitch, UiButton],
  template: `
    <div
      ui-cluster
      [gap]="'s'"
      [align]="'center'"
    >
      <label
        ui-switch
        [size]="'sm'"
      >Switch</label>
      <ng-template
        ui-button
        [size]="'sm'"
      >Small button</ng-template>
    </div>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: label

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
checkedWhether the switch is checked initially or when set externally. A form reset returns it to this.checkedbooleanfalse—
disabledDisables the native switch, so it cannot be toggled or focused.disabledbooleanfalse—
nameThe native name the switch submits under: a form that contains it sends name=value while it is on, and nothing while it is off, as a native checkbox does. Left empty, it takes no part in form submission.namestring—
requiredMarks the native switch required, so native form validation treats it as invalid until it is on.requiredbooleanfalse—
sizeAligns the switch with fields and buttons of the same size. It keeps its track size; the label takes that size's field type size and its first line centres in that control height (sm 2rem, lg 3rem by default), so in a row of inputs, selects, and buttons of one size it shares their height and their text baseline. Touch input keeps the same visible alignment. md is the standard layout.sizekeywordmdsm, md, lg
valueThe value a form submits under name while the switch is on, also reported in the change event's detail so one handler can tell switches apart. on by default, as on a native checkbox.valuestringon—

Framework props

NameTypeDefaultAllowed values
checkedbooleanfalse—
disabledbooleanfalse—
namestring—
requiredbooleanfalse—
size"sm" | "md" | "lg"mdsm, md, lg
valuestringon—

Events

NameDetailDescription
changeobject
  • checked: boolean (required)
  • value: string (required)
  • trigger: keyword (required)
Emitted when a toggleable control changes checked state.

Slots

NamePurpose
defaultDefault child content.
descriptionNamed content for the description region.

Design tokens

Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.

Component tokens

TokenDeclared valuesFallback
--ui-switch-gap—var(--ui-space-2)
--ui-switch-track-height—var(--ui-choice-track-height) | 1.5rem
--ui-switch-track-width—var(--ui-choice-track-width) | 2.5rem

Shared tokens

TokenFallback
--ui-accent-highlightnone
--ui-accent-solid—
--ui-border-strong—
--ui-border-width—
--ui-choice-hover-highlightnone
--ui-choice-hover-surface—
--ui-choice-track-height—
--ui-choice-track-width—
--ui-control-bordervar(--ui-border-strong)
--ui-control-border-hovervar(--ui-border-strong)
--ui-control-inset—
--ui-control-size-lg—
--ui-control-size-sm—
--ui-control-surfacevar(--ui-surface-default)
--ui-disabled-border—
--ui-disabled-surface—
--ui-disabled-text—
--ui-focus-ringvar(--ui-accent-solid)
--ui-focus-width—
--ui-font-size-md—
--ui-font-size-sm—
--ui-knob-highlightnone
--ui-knob-on-accent-highlightnone
--ui-line-height—
--ui-line-height-md—
--ui-motion-base—
--ui-motion-ease—
--ui-neutral-wash-hover—
--ui-radius-round—
--ui-shadow-xs—
--ui-space-1—
--ui-space-2—
--ui-surface-default—
--ui-surface-highlightnone | linear-gradient(transparent, transparent)
--ui-text-on-accent—
--ui-text-primary—
--ui-text-secondary—