Name any control with a label, and let a press on the label reach it.
A plain <label> only reaches inputs, buttons and selects. ui-label names any control by its for, including a radio group or a slider, and a press on the label moves focus into it.
import "@ui-sdk/web/label"
import "@ui-sdk/web/radio"
const speeds = [
{ value: "standard", label: "Standard" },
{ value: "express", label: "Express" },
{ value: "overnight", label: "Overnight" },
]
function mount(root: HTMLElement) {
root.innerHTML = `
<div data-slot="stack">
<ui-label for="shipping-speed">Shipping speed</ui-label>
<ui-radio-group id="shipping-speed" default-value="standard">
${speeds
.map(
(speed) => `
<ui-label>
<ui-radio value="${speed.value}">
<ui-radio-indicator></ui-radio-indicator>
</ui-radio>
${speed.label}
</ui-label>
`
)
.join("")}
</ui-radio-group>
</div>
`
}
export { mount }Installation
npm install @ui-sdk/web @ui-sdk/coreImporting the component defines its element. On the server the import does nothing.
import "@ui-sdk/web/label"Usage
Import the label and point its for at the control's id:
<script type="module">
import "@ui-sdk/web/label"
import "@ui-sdk/web/radio"
</script>
<ui-label for="plan">Plan</ui-label>
<ui-radio-group id="plan" default-value="pro">
<ui-radio value="free">Free</ui-radio>
<ui-radio value="pro">Pro</ui-radio>
</ui-radio-group>The control takes ui-label as its name, and a press on the label focuses it, checks it or clicks it, as a native label would.
Inside a Field, use ui-field-label, which also ties the field's description and errors to the control.
Build Your Own
@ui-sdk/web/label comes with a ready-made ui-label element. If you'd like to build your own, with your own element name, start from the label primitive in @ui-sdk/core:
import { label } from "@ui-sdk/core/label"
import { createParts } from "@ui-sdk/web"
createParts(label, {
root: { tag: "app-caption" },
})<app-caption for="plan">Plan</app-caption>app-caption keeps the behavior of the ready-made ui-label: it names the control and reaches it on a press. See Rendering and Custom Parts for more.
Slider
A label for a slider names its thumb, so the thumb reads as Volume, and a press on the label focuses it.
import "@ui-sdk/web/label"
import "@ui-sdk/web/slider"
function mount(root: HTMLElement) {
root.innerHTML = `
<ui-slider id="volume" default-value="75">
<div data-slot="row">
<ui-label for="volume">Volume</ui-label>
<ui-slider-value></ui-slider-value>
</div>
<ui-slider-control>
<ui-slider-track>
<ui-slider-indicator></ui-slider-indicator>
<ui-slider-thumb></ui-slider-thumb>
</ui-slider-track>
</ui-slider-control>
</ui-slider>
`
}
export { mount }Switch
Point the label at a switch beside it. A press on the label turns the switch on and off.
import "@ui-sdk/web/label"
import "@ui-sdk/web/switch"
function mount(root: HTMLElement) {
root.innerHTML = `
<div data-slot="row">
<ui-switch id="updates">
<ui-switch-thumb></ui-switch-thumb>
</ui-switch>
<ui-label for="updates">Email me product updates</ui-label>
</div>
`
}
export { mount }Wrapping a Control
Put the control inside the label instead of pointing at it, and you need no id.
import "@ui-sdk/web/checkbox"
import "@ui-sdk/web/label"
import { Check, createElement } from "lucide"
function mount(root: HTMLElement) {
root.innerHTML = `
<ui-label>
<ui-checkbox>
<ui-checkbox-indicator>
${createElement(Check, { "aria-hidden": "true" }).outerHTML}
</ui-checkbox-indicator>
</ui-checkbox>
Accept terms and conditions
</ui-label>
`
}
export { mount }Styling
The label is unstyled. It has a data-slot of label.
A double click on the label doesn't select its text, so you don't need user-select: none for that.
API Reference
Attributes are the same as below, along with for, which names the control's id.
Label
debug?boolean- Logs every state change with its reason to the console, in development builds.
Accessibility
ui-label names its control through aria-labelledby, unless the control has an aria-label or an aria-labelledby of its own, which win. Its press does what a native label's does: it checks a checkbox, a switch or a radio, focuses a group's checked radio, and focuses anything else. A press on a disabled control does nothing.
Tested Behavior
Every line below is an automated test.
- A label names a native input, and a press focuses it.
- A label names a radio group, and a press focuses its checked radio.
- A label for a slider names its thumb, and a press focuses it.
- A label around a checkbox names it, and a press toggles it.
- A press on the label of a disabled group moves no focus.
- A control its author names keeps that name.
- A label names a control that renders after it.
- A double click on a label doesn't select its text.
Runs in React, Vue and web components.