Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Input

A styled Base UI input — all input types, Field state data attributes, error and disabled states, focus border recolor, and optional prefix/suffix addons.

Status
stable
Since
0.1.0
Accessibility pattern
native input

Last updated

Install

Add Input from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/input

Usage

import { Input } from "@/components/ui/input";

<Input type="email" placeholder="you@vegastack.com" />;

Input is built on Base UI Input, so standard input attributes work alongside Base UI render, onValueChange, Field state data attributes, and state-function className. Its ref forwards to the underlying input element.

Examples

States

The field reflects disabled, readOnly, and aria-invalid. The invalid state re-colors the border with the destructive-border token (aria-invalid:border-destructive-border/(--alpha-tint-border)) — pair it with a visible error message for non-color-dependent feedback. While the field is focused the focus tint wins the border and the invalid tint returns on blur: a text field has no outline, so the border is its only focus cue and nothing else may hold it.

Input types

Input forwards any HTML type to the underlying <input>number, tel, url, email, password, and the rest all work alongside the shared field styling. type defaults to 'text'.

Don't use type="date", "time", or "datetime-local". They summon the browser's native picker, which ignores every design token — its own surface, radius, type ramp and focus ring, rendered differently on each browser and OS. Reach for DatePicker instead: it's built from Popover + Calendar, themed like the rest of the system, and displays dates in the readable Jun 24, 2026 format (any Intl.DateTimeFormat options via formatOptions).

Addons

Pass a prefix and/or suffix to render non-editable content inside the field — a domain like app.vegastack.com/, a unit, or an icon. In addon mode the <input> is wrapped in a bordered group, and focus/invalid/disabled styling moves to the wrapper so the whole field reacts as one unit. Use className for the Base UI input and containerClassName for the addon wrapper.

app.vegastack.com/
.vegastack.com

In addon mode the wrapper reads the inner input's state through has-* selectors, so disabled and aria-invalid recolor the whole group rather than just the <input>has-disabled:* dims the group and has-aria-invalid:border-destructive-border/(--alpha-tint-border) marks it invalid. As on a bare field, the group's focus tint outranks the invalid one while anything inside it holds focus.

app.vegastack.com/

Controlled value (onValueChange)

Base UI exposes onValueChange, which receives the next string value directly (no event unwrapping) — convenient for controlled fields and derived UI. It fires alongside the native onChange.

const [value, setValue] = useState("");

<Input
  value={value}
  onValueChange={setValue}
  placeholder="Type a workspace name"
/>;

Slug preview appears here

Polymorphism (render)

Use Base UI's render prop to swap the underlying element while keeping the field styling and behavior — pass an element to merge props into, or a function for full control.

<Input render={<input data-testid="rendered-input" />} type="email" />

Playground

Try every type, size, and state — flipping Invalid on replays the built-in shake — then copy the generated JSX.

<Input placeholder="you@vegastack.com" />

API Reference

PropTypeDefaultDescription
classNamestring | ((state: InputState) => string | undefined)Classes for the Base UI input element. Accepts Base UI's state-function form, so styles can respond to field state such as focused or invalid.
containerClassNamestringClasses for the wrapper used only when prefix or suffix is present.
prefixReact.ReactNodeContent rendered as a non-editable addon before the input (e.g. "app.vegastack.com/" or an icon). Switches the component into addon mode: the <input> is wrapped in a bordered group and the border/ring/disabled styling moves to the wrapper. Plain strings render as muted, non-selectable label text.
size"lg" | "md" | "sm"'md'Control height on the shared 28/32/40 scale (--size-sm/md/lg), matching Button and Select. (The native numeric size attribute is intentionally replaced by this variant prop.)
suffixReact.ReactNodeContent rendered as a non-editable addon after the input (e.g. a unit like ".com" or an icon). Switches the component into addon mode (see prefix).

Data attributes and CSS variables on Input

AttributeValues
data-field-group""
data-sizemirrors a prop or state value
data-slot"input" | "input-group" | "input-prefix" | "input-suffix"

Accessibility

  • Renders Base UI Input as a native <input> by default — always associate a visible <label> (wrap it or use htmlFor/id); use aria-label only when a visible label is impossible.
  • On focus the field recolors its border with the ring token (focus:border-ring/(--alpha-tint-border)) instead of removing the outline — never outline: none with no replacement affordance. Textarea and OTPInput use the identical border tint, so text-entry focus reads the same across all three.
  • Set aria-invalid (and aria-describedby pointing at the error text) to mark a field invalid; the styling is token-driven and never relies on color alone.
  • prefix/suffix addons are decorative label text; keep the meaningful value in the input itself so assistive tech reads the field, not the chrome.
ContractStates tested
Behaviourdefault, disabled, dragging, error, invalid
Accessibilityinvalid, semantic-html
Visualdefault, focus, disabled, invalid, error, dark

Do / Don't

Do
Pair every input with a label and, when invalid, an aria-describedby error message.
Don't
Use aria-invalid styling as the only error signal, or rely on placeholder text as a label.

On this page