<Stack
width="440px"
maxWidth="100%"
>
<TextField.Root
size="sm"
variant="outline"
>
<TextField.Label>Project name</TextField.Label>
<TextField.Input name="playground-project" placeholder="Website redesign" />
<TextField.HelperText>Visible to everyone in your workspace.</TextField.HelperText>
<TextField.ErrorMessage>Enter a project name.</TextField.ErrorMessage>
</TextField.Root>
</Stack>Features
- Supports
outlineandfillvariants with responsive sizes. - Supports start and end icons, affixes, and custom slots.
- Shows helper text and error messages under the input.
- Accepts native input types like
emailandpassword. - Supports controlled and uncontrolled values.
Anatomy
Root links the label, helper text, and error to the input, Label names it, and Input is the field and requires name. HelperText is optional.
<TextField.Root>
<TextField.Label>Email address</TextField.Label>
<TextField.Input name="email" />
<TextField.HelperText>We will use this address for project updates.</TextField.HelperText>
</TextField.Root>Variants
Text Field has two visual styles: outline and fill. The default is outline.
<TextField.Root variant="outline">
<TextField.Label>Outline</TextField.Label>
<TextField.Input name="outline-name" placeholder="Project name" />
</TextField.Root>
<TextField.Root variant="fill">
<TextField.Label>Fill</TextField.Label>
<TextField.Input name="fill-name" placeholder="Project name" />
</TextField.Root>Sizes
Root size scales the input, label, and icons. The default is sm.
<TextField.Root size="sm">
<TextField.Label>Small</TextField.Label>
<TextField.Input name="small-name" defaultValue="Website redesign" />
</TextField.Root>
<TextField.Root size="md">
<TextField.Label>Medium</TextField.Label>
<TextField.Input name="medium-name" defaultValue="Website redesign" />
</TextField.Root>Responsive
size also accepts a breakpoint object.
<TextField.Root size={{ xs: "sm", md: "md" }}>
<TextField.Label>Project name</TextField.Label>
<TextField.Input name="responsive-name" defaultValue="Website redesign" />
</TextField.Root>Icons and Affixes
startIcon and endIcon add an icon that focuses the input on click. prefix and suffix add text tied to the value, like a currency. An icon and affix can share a side.
import { Search } from "@vortexlabs/vortex-icons";
<TextField.Root>
<TextField.Label>Search projects</TextField.Label>
<TextField.Input name="project-search" startIcon={<Search />} placeholder="Search by name" attributes={{ type: "search" }} />
</TextField.Root>
<TextField.Root>
<TextField.Label>Budget</TextField.Label>
<TextField.Input name="budget" prefix="$" suffix="AUD" defaultValue="1200" attributes={{ inputMode: "decimal" }} />
</TextField.Root>Custom Slots
startSlot and endSlot hold other content, like an action. A slot replaces the icon and affix on its side. This example also controls the value with value and onChange.
"use client";
import { Button, TextField } from "@vortexlabs/vortex";
import { useState } from "react";
export const TextFieldClearExample = () => {
const [value, setValue] = useState("Website redesign");
return (
<TextField.Root>
<TextField.Label>Project name</TextField.Label>
<TextField.Input
name="project-name"
value={value}
onChange={({ value }) => setValue(value)}
endSlot={
<Button size="xs" variant="tertiary" disabled={!value} onClick={() => setValue("")}>
Clear
</Button>
}
/>
<TextField.HelperText>{value.length} characters</TextField.HelperText>
</TextField.Root>
);
};Multiline
Root multiline lets slot content wrap onto several lines, like a list of tags.
import { Badge, TextField } from "@vortexlabs/vortex";
<TextField.Root multiline width="320px">
<TextField.Label>Tags</TextField.Label>
<TextField.Input
name="tags"
placeholder="Add a tag"
startSlot={["Design", "Research", "Marketing", "Engineering", "Support"].map((tag) => (
<Badge size="sm" key={tag}>{tag}</Badge>
))}
/>
</TextField.Root>Validation
Root error marks the input invalid and shows TextField.ErrorMessage; helper text stays visible. required only adds native validation, so you decide when to set error.
<TextField.Root error required>
<TextField.Label>Workspace name</TextField.Label>
<TextField.Input name="workspace-name" defaultValue="a" attributes={{ minLength: 3 }} />
<TextField.ErrorMessage>Use at least three characters.</TextField.ErrorMessage>
<TextField.HelperText>This name is visible to everyone in your workspace.</TextField.HelperText>
</TextField.Root>Disabled and Read Only
Root disabled blocks editing and focus. readOnly in Input attributes keeps the value focusable and selectable.
<TextField.Root disabled>
<TextField.Label>Workspace slug</TextField.Label>
<TextField.Input name="disabled-slug" defaultValue="design-team" />
</TextField.Root>
<TextField.Root>
<TextField.Label>Workspace ID</TextField.Label>
<TextField.Input name="workspace-id" defaultValue="ws_12345" attributes={{ readOnly: true }} />
</TextField.Root>Native Attributes and Width
Input attributes sets native options like type, autoComplete, and minLength; type defaults to text. Root width accepts a CSS length or a breakpoint object.
<TextField.Root width={{ xs: "100%", md: "24rem" }}>
<TextField.Label>Password</TextField.Label>
<TextField.Input name="password" attributes={{ type: "password", autoComplete: "new-password", minLength: 8 }} />
<TextField.HelperText>Use at least eight characters.</TextField.HelperText>
</TextField.Root>Accessibility
TextField.Label; a placeholder doesn't replace it. Helper text and the error message are linked to the input automatically.