Skip to content
fayeuiStatic / 0.1.0
LLMs
Components / Static edition

Text fields and forms

Native form values, validation, adornments, multiline input and reset.

Quick start

After the application setup, copy this component into your application. It imports only public packages; the complete live demo later on this page also uses documentation layout helpers.

'use client';
import { useState } from 'react';
import { TextField } from '@fayegram-ai/faye-ui/controls/text-field';
import { TextareaField } from '@fayegram-ai/faye-ui/controls/textarea-field';
import { Button } from '@fayegram-ai/faye-ui/controls/button';
import { Stack } from '@fayegram-ai/faye-ui/layout/layout';

export function ContactForm() {
  const [email, setEmail] = useState('');
  const [error, setError] = useState<string>();
  return <form noValidate onSubmit={event => {
    event.preventDefault();
    setError(event.currentTarget.checkValidity() ? undefined : 'Enter a valid email address.');
  }}>
    <Stack gap="4">
      <TextField name="email" type="email" label="Email" required
        description="We will use this to reply." error={error}
        value={email} onChange={event => setEmail(event.target.value)} />
      <TextareaField name="message" label="Your message" autosize minRows={3} maxRows={6}
        description="Add any details that will help us." />
      <Button type="submit">Check details</Button>
    </Stack>
  </form>;
}

API reference: Text Field, Textarea Field, Button.

Usage contract

TextField is a convenience composition. Field and Fieldset expose validation anatomy. InputAdornment is a sibling, not a hidden label substitute. AutosizeTextarea measures browser geometry after mounting and remains a normal textarea for forms.

AutosizeTextarea recalculates its row bounds after React updates, including changes to typography, padding, and classes, as well as edits and observed width changes. Its form prop can associate it with an external form, including one mounted later or replaced without changing its ID. Native resets restore both the value and the measured height, and repeating an edit after a reset still resizes the field. A canceled reset preserves the draft and its height. Content beyond maxRows scrolls within the field.

Customization

Use semantic tokens for visual changes, public component parts for structural customization, and explicit props for instance behavior. The surrounding site lets you compare light and dark, different brands and density.

Source and verification

The examples below import actual public package entries. Full source is downloadable. Source coverage does not establish keyboard, hydration or browser compatibility; those require the application test suite.

Disabled and read-only composition

Disabled surface styling takes precedence when an input or textarea also has readOnly. This applies to standalone fields and InputGroup frames, including frames that infer disabled state from their input. The distinction remains correct when a custom theme gives the two states different surface colors.

Live examples

Try the controls below, then read or copy the complete example to adapt it for your application.

Native submission and validation

A persistent label, not a placeholder.
Contact details

Collected only in this local example.

Use a valid email format.

Submit to inspect native FormData.

Text areas and states

https://
Shared label and hint anatomy.
This name is already in use.

Advanced field composition

Compose a collection

This local form deliberately fails its first save so draft retention and retry can be tried. No server is contacted.

Enter a destination.
Type a city. In this local example, “fail” simulates a retryable error.
Read the complete example
'use client';
import { ArrowUpRight } from '@lucide/icons';
import { Icon } from '@fayegram-ai/faye-ui/core/icon';
import { useState } from 'react';
import { InputGroup, InputAdornment } from '@fayegram-ai/faye-ui/controls/input-group';
import { Input, Textarea } from '@fayegram-ai/faye-ui/controls/input';
import { AutosizeTextarea } from '@fayegram-ai/faye-ui/controls/autosize-textarea';
import { TextField } from '@fayegram-ai/faye-ui/controls/text-field';
import { NativeSelect } from '@fayegram-ai/faye-ui/controls/native-select';
import { Field } from '@fayegram-ai/faye-ui/controls/field';
import { FieldLayout } from '@fayegram-ai/faye-ui/controls/field-layout';
import { Fieldset } from '@fayegram-ai/faye-ui/controls/fieldset';
import { Form } from '@fayegram-ai/faye-ui/controls/form';
import { Button } from '@fayegram-ai/faye-ui/controls/button';
import { Stack, Inline } from '@fayegram-ai/faye-ui/layout/layout';
import { Section, Result } from '../example';
import AdvancedFields from './advanced-fields';
export default function Fields() {
    const [result, setResult] = useState('Submit to inspect native FormData.');
    return <><Section title="Native submission and validation"><Form onSubmit={e => { e.preventDefault(); setResult(JSON.stringify(Object.fromEntries(new FormData(e.currentTarget)), null, 2)); }}><Stack gap="4"><TextField label="Workspace name" name="workspace" required defaultValue="Northstar" description="A persistent label, not a placeholder."/><Fieldset.Root aria-describedby="contact-example-description"><Fieldset.Legend>Contact details</Fieldset.Legend><p id="contact-example-description">Collected only in this local example.</p><Field.Root name="email"><Field.Label>Email</Field.Label><Field.Control render={<Input />} type="email" required placeholder="name@example.com"/><Field.Description>Use a valid email format.</Field.Description><Field.Error match="valueMissing">An email address is required.</Field.Error><Field.Error match="typeMismatch">Enter a valid email address.</Field.Error><Field.Validity>{state => state.validity?.valid === true ? <small>Email format accepted.</small> : null}</Field.Validity></Field.Root></Fieldset.Root><label className="docs-field">Role<NativeSelect name="role" defaultValue="member"><option value="member">Member</option><option value="viewer">Viewer</option></NativeSelect></label><Inline><Button type="submit" variant="solid" tone="accent">Inspect values</Button><Button type="reset">Reset form</Button></Inline><Result>{result}</Result></Stack></Form></Section><Section title="Text areas and states"><Stack gap="4"><InputGroup><InputAdornment>https://</InputAdornment><Input aria-label="Website hostname" placeholder="example.com"/><InputAdornment position="end"><Icon icon={ArrowUpRight} size="sm"/></InputAdornment></InputGroup><FieldLayout label="Summary" description="Shared label and hint anatomy.">{attrs => <Textarea {...attrs} defaultValue="A short project description."/>}</FieldLayout><label className="docs-field">Expanding notes<AutosizeTextarea aria-label="Expanding notes" minRows={2} maxRows={7} placeholder="Add several lines…"/></label><TextField label="Invalid field" defaultValue="Taken" error="This name is already in use."/><TextField label="Read-only field" value="Immutable identifier" readOnly/><TextField label="Disabled field" disabled defaultValue="Unavailable"/></Stack></Section><Section title="Advanced field composition"><AdvancedFields/></Section></>;
}