import { Label, TextField, TextFieldInput } from '@vega-ui/react';
<div>
<Label htmlFor='preview-label'>Work email</Label>
<TextField><TextFieldInput id='preview-label' type='email' placeholder='maria@northwind.io' /></TextField>
</div>- Browser support
- Widely available
- Chrome 111
- Edge 111
- Firefox 113
- Safari 15.4
- Safari on iOS 15.4
- Bundle size
- Oklab and OkLCh — comes from the design tokens
- :where()
Summary
Label is the semantic label primitive for form fields. It reuses the Text typography contract while rendering a native <label> element, so naming a control stays separate from generic text styling.
Imports
import { Label, type LabelProps } from '@vega-ui/react';Exported Types
LabelProps
Basic Usage
import { Label, TextField, TextFieldInput } from '@vega-ui/react';
import { useId } from 'react';
export default function FieldLabel() {
const id = useId();
return (
<div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%' }}>
<Label htmlFor={id}>Company name</Label>
<TextField>
<TextFieldInput id={id} name='company' autoComplete='organization' />
</TextField>Variants
- Size: follows the label size contract from
LabelProps - Composition: native label element with VegaUI text styling
- Integration: direct
htmlForusage or wrapping a control
Edge Cases
- Wrapping a control and using
htmlForat the same time is usually unnecessary. Labelinherits theTexttypographic model, so visual changes can affect field-label consistency.- Interactive content inside a label can create ambiguous click behavior.
Labelshould name one control or one wrapped input concept; group labeling belongs toFieldsetLegend.
Common Mistakes
- Using placeholder text as the only visible label.
- Using
Labelfor static section headings instead of form controls. - Putting multiple unrelated controls inside one label wrapper.
Examples
Basic
Basic: label for a text field
htmlFor must match the id of the native input — TextFieldInput, not the TextField wrapper. useId keeps the id unique when the same form renders more than once. Clicking the label focuses the input.
import { Label, TextField, TextFieldInput } from '@vega-ui/react';
import { useId } from 'react';
export default function FieldLabel() {
const id = useId();
return (
<div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%' }}>
<Label htmlFor={id}>Company name</Label>
<TextField>
<TextFieldInput id={id} name='company' autoComplete='organization' />
</TextField>Form/Integration
Form/Integration: labels for composite fields
In composite fields the id goes on the focusable part: SelectCombobox, NumberFieldInput, or the TextArea itself. The label then names that part, and clicking it moves focus there.
import {
Label,
NumberField,
NumberFieldDecrementButton,
NumberFieldIncrementButton,
NumberFieldInput,
Select,
SelectCombobox,
SelectHiddenSelect,
SelectIcon,
SelectListbox,
SelectOption,Form/Integration: wrapping label without ids
Wrapping a control inside Label associates them implicitly, so neither htmlFor nor id is needed. It fits controls that render a single native element, like TextArea and Radio; TextField renders a <div> wrapper that a label must not contain, and Checkbox and Switch roots are labels already.
import { Label, Radio, TextArea } from '@vega-ui/react';
export default function WrappingLabel() {
return (
<div style={{ display: 'grid', gap: 'var(--spacing-8)', width: '100%' }}>
<Label style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
Delivery instructions
<TextArea name='instructions' rows={3} fullWidth placeholder='Gate code, floor, where to leave the parcel' />
</Label>
<div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
<Label fontWeight={400} style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-4)' }}>
<Radio name='window' value='morning' defaultChecked />Layout/Overlay
Layout/Overlay: labels beside the fields
A two-column grid puts labels to the left of their fields. display: contents on each row keeps label and field as direct grid items, and long labels wrap inside their column instead of pushing the field.
import { Label, TextField, TextFieldInput } from '@vega-ui/react';
import { useId } from 'react';
const FIELDS = [
{ name: 'firstName', label: 'First name', autoComplete: 'given-name' },
{ name: 'lastName', label: 'Last name', autoComplete: 'family-name' },
{ name: 'jobTitle', label: 'Job title shown on your public profile', autoComplete: 'organization-title' },
];
export default function SideBySideLabels() {
const id = useId();
Error
Error: required marker and error message
The asterisk is decorative and hidden from screen readers; required on the input is what announces the field as required. The label stays unchanged on error — the message under the field carries it, linked with aria-describedby.
import { Button, HelperText, Label, TextField, TextFieldInput } from '@vega-ui/react';
import { FormEvent, useId, useState } from 'react';
export default function RequiredFieldError() {
const id = useId();
const messageId = `${id}-message`;
const [taxId, setTaxId] = useState('');
const [submitted, setSubmitted] = useState(false);
const invalid = submitted && taxId.trim() === '';
const onSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();Disabled
Disabled: label of a disabled field
Label has no disabled style of its own, so dim it together with the field it names. Here the checkbox disables the address field, and the label follows.
import {
Checkbox,
CheckboxCheckedIcon,
CheckboxIndicator,
CheckboxInput,
Label,
TextField,
TextFieldInput,
} from '@vega-ui/react';
import { useId, useState } from 'react';
export default function LabelForDisabledField() {Edge
Edge: visually hidden label
When the design has no room for a visible label, keep a real <label> and hide it with VisuallyHidden. Screen readers and voice control still get the name, and the placeholder stays only an example.
import { Button, Label, TextField, TextFieldInput, VisuallyHidden } from '@vega-ui/react';
import { useId } from 'react';
export default function VisuallyHiddenLabel() {
const id = useId();
return (
<form role='search' style={{ display: 'flex', gap: 'var(--spacing-4)', width: '100%' }}>
<VisuallyHidden asChild>
<Label htmlFor={id}>Search the help center</Label>
</VisuallyHidden>
<TextField style={{ flex: 1 }}>Anatomy
Overview
Label is a single-part primitive. It renders a semantic <label> and borrows typography behavior from Text.
Required Parts
Label
Required. It is both the semantic root and the visible label surface.
Optional Parts
There are no exported child parts.
Composition Order
Label- label content
Valid Composition Patterns
import { Label, TextField, TextFieldInput } from '@vega-ui/react';
import { useId } from 'react';
export default function FieldLabel() {
const id = useId();
return (
<div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%' }}>
<Label htmlFor={id}>Company name</Label>
<TextField>
<TextFieldInput id={id} name='company' autoComplete='organization' />
</TextField>Invalid Composition Patterns
Placeholder used instead of a label
This removes a stable visible label.
Multiple unrelated controls wrapped by one label
Click behavior and semantics become unclear.