V2 componentsUpdated Sep 9, 2026
Input
A single-line text field. Thin wrapper over Base UI Input so native attributes, refs, and form integration all work as expected.
Source: packages/ui/src/components/input
import { Input, inputStyles } from '@highlight/ui'Change the props; the preview and the snippet update together.
<Input placeholder="Search meetings…" />import { Input } from '@highlight/ui' export default function InputBasic() { return ( <div className="flex w-72 flex-col gap-3"> <Input placeholder="Search meetings…" /> <Input defaultValue="Weekly design sync" /> <Input placeholder="Disabled" disabled /> </div> )}Button’s own 24 / 28 / 32 / 40 heights, so a field and a button in one row line up. Density only: the type stays 13px.
import { CONTROL_SHELL_SIZES, Input } from '@highlight/ui' export default function InputSizes() { return ( <div className="flex w-72 flex-col gap-3"> {CONTROL_SHELL_SIZES.map((size) => ( <Input key={size} size={size} placeholder={size} /> ))} </div> )}Field pairs the label and note with the control and generates the wiring, so no ids are passed by hand. autocomplete and inputmode still come from the consumer; placeholders end with an ellipsis.
import { Field, Input } from '@highlight/ui' export default function InputLabelled() { return ( <form className="flex w-72 flex-col gap-4" onSubmit={(event) => event.preventDefault()}> <Field name="name"> <Field.Label>Display name</Field.Label> <Input autoComplete="name" placeholder="Ada Lovelace…" /> </Field> <Field name="email"> <Field.Label>Work email</Field.Label> <Input type="email" inputMode="email" spellCheck={false} placeholder="ada@company.com…" /> <Field.Note>We only use this for meeting invites.</Field.Note> </Field> </form> )}left and right put a node inside the box: a glyph, or a ghost icon-only Button one size down from the input, such as a password reveal. It needs an aria-label. The shell moves to a wrapper, so the edge, focus ring, invalid and disabled states still cover the whole box, and the Field wiring stays on the input.
import { useState } from 'react'import { Eye, EyeOff, Search } from 'lucide-react'import { Button, Field, Input } from '@highlight/ui' export default function InputSlots() { const [revealed, setRevealed] = useState(false) return ( <form className="flex w-72 flex-col gap-4" onSubmit={(event) => event.preventDefault()}> <Input left={<Search />} placeholder="Search meetings…" /> <Field name="password"> <Field.Label>Password</Field.Label> <Input type={revealed ? 'text' : 'password'} autoComplete="current-password" placeholder="Your password…" right={ <Button type="button" variant="ghost" size="sm" shape="square" aria-label={revealed ? 'Hide password' : 'Show password'} aria-pressed={revealed} onClick={() => setRevealed((value) => !value)} > {revealed ? <EyeOff /> : <Eye />} </Button> } /> </Field> </form> )}The root, then any compound sub-components. Every part accepts className and forwards the rest of its props to the element or Base UI part beneath it.
Input- Base UI Input in the shared control shell. size sets the height; every input prop passes through.
Props added on top of the underlying element or Base UI part.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Merged last with twMerge, so any default is overridable. |
left | ReactNode | — | A node before the text, inside the box. Same name as Button’s left. |
right | ReactNode | — | A node after the text, inside the box. With left, puts the shell on a wrapper; className lands there. |
value | string | — | Controlled value. Pair it with onValueChange. |
defaultValue | string | — | Initial value when uncontrolled. |
onValueChange | (value: string, details) => void | — | Base UI’s change callback; the second argument carries the reason. |
- Every native <input> attribute passes through: type, placeholder, disabled, readOnly, required, maxLength, name, autoComplete, inputMode. A ref reaches the element.
- The shell (ground, 0.5px edge, the size’s corner smoothed by corner-control, hover, focus, invalid and disabled) is controlShellVariants, shared with Select.Trigger and Combobox.Input. inputStyles adds only the placeholder; a custom field takes both.
- Invalid comes from aria-invalid, which Field sets when the control fails validation: a danger edge, darker on hover, and a danger ring on focus.