Skip to content
Highlight

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

tsx
import { Input, inputStyles } from '@highlight/ui'

Change the props; the preview and the snippet update together.

Props
Size
Type
Disabled
Read only
tsx
<Input placeholder="Search meetings…" />
tsx
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.

tsx
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.

We only use this for meeting invites.

tsx
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.

tsx
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.

PropTypeDefaultDescription
classNamestring—Merged last with twMerge, so any default is overridable.
leftReactNode—A node before the text, inside the box. Same name as Button’s left.
rightReactNode—A node after the text, inside the box. With left, puts the shell on a wrapper; className lands there.
valuestring—Controlled value. Pair it with onValueChange.
defaultValuestring—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.