Label
Renders an accessible label associated with controls.
src/components/rahti_ui/label.rs
Basics
`for` names the control with the matching `id`, which is what makes the label clickable and what a screen reader reads. It is spelled `r#for` on a tag — a prop names a Rust parameter, and `for` is a keyword — and renders as `for`.
Wrapping a control
The other association HTML gives a label, and the one that needs no ids: a control written inside the label is named by it. The second example overrides `items-center` to `items-start` so a two-line description lines up with the box rather than centring against it.
More than text
`flex items-center gap-2` is in the class list, so an icon or a badge beside the text needs no layout at the call site. Unlike a Button, a Label has no `[&_svg]` rules, so an icon brings its own `size-4`.
Disabled, by a peer
`peer-disabled:cursor-not-allowed peer-disabled:opacity-50` styles a label whose sibling is disabled. Tailwind compiles it to `.peer:disabled ~ &`, so the control needs `class="peer"` and has to come before the label in the markup — `flex-col-reverse` puts it back above visually. These are ordinary `<Label>` tags: a Label with no override renders as one literal `<label>` carrying its own name, so it is a real sibling of the field and the combinator reaches it.
Disabled, by a group
`group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50` reads an ancestor instead of a sibling, so the label can be written first. `data-disabled="false"` does not match — the selector asks for the value, not just the attribute.
Attributes
`.attr(…)` writes anything; `.unset(…)` takes one away, which is the thing a value cannot express and the reason a forwarded set is an `Attrs` rather than a list of pairs. `.id(…)` is for the other direction — a control that points back with `aria-labelledby`.
Class override
`class` is merged, not appended: a utility written here replaces the one it conflicts with and leaves the rest alone — `text-base` takes `text-sm`'s place, `gap-4` takes `gap-2`'s. The merge is `twMerge`'s, in the browser, so unlike every other label on this page these four are unstyled until PulsePoint mounts.
Content that is the page's state
A `{…}` written between a label's tags is compiled in the block it was written in. Children handed to a component tag were written somewhere else, so a label whose text moves belongs on markup this page owns — `label_variants` returns the same class list the component renders, and a native `<label>` here is a Label in every way that shows.
Clicking the label focuses the field — that is `for` and `id`, not JavaScript.
An attribute that is the page's state
`.render(children)` returns the element itself, with no boundary around it, so interpolating it into this block puts it in this block's scope and a binding written on it resolves. `for` is bound below: the label names whichever field is targeted, and clicking it focuses that one.
Disabled, driven by state
The `group-data-[disabled=true]:` utilities read an ancestor, so the ancestor is what changes. `data-disabled` is bound on the wrapper here; the label dims and stops taking clicks without anything being written on the label at all.
The binding is `String(locked)` rather than `locked`, and the difference is not cosmetic: PulsePoint writes a `true` as a bare boolean attribute — `data-disabled=""` — which is right for `disabled` on the field below and wrong here, because the selector Tailwind compiles asks for `[data-disabled="true"]` by value.
The component tag, and its boundary
The tag is the right call for everything above the reactive sections — a `for`, a class override, an icon beside the text. A click on it is resolved when it fires, late enough to reach this page, so `onclick={…}` works through the tag the way it does on a Button.
Where the tag stops
An attribute binding does not cross it. `html!` hands the expression over as source, and it is compiled in the scope the element is mounted in — which through a tag is the component's, where nothing this page declared exists.
// Compiles against a scope with no `target`, and does nothing.
<Label r#for={target}>"Email"</Label>Children are the other half of it: a `{…}` between the tags was written where the tag was written, not where the label lands. The two working spellings are the first two reactive sections — `label_variants` on a native `<label>` for content, `.render()` interpolated into this block for attributes.