Form components
The core form controls ship in every Pelagic UI package. Labels are always linked, hints and errors are announced through aria-describedby, and invalid fields set aria-invalid.
9 components on this page. Every demo is live — change the theme in the header or flip a demo to right-to-left with its RTL button.
Button Link to Button
Five variants, three sizes, a loading state with aria-busy and icon-only buttons with an accessible name.
Code
<Button>Save</Button>
<Button variant="outline">Preview</Button>
<Button variant="danger" loading>Deleting</Button> <Button>Save</Button>
<Button variant="outline">Preview</Button>
<Button variant="danger" loading>Deleting</Button> <Button>Save</Button>
<Button variant="outline">Preview</Button>
<Button variant="danger" loading>Deleting</Button> ---
import Button from '@pelagic-ui/astro/components/Button';
---
<Button>Save</Button>
<Button variant="outline">Preview</Button> <Button title="Save" />
<Button title="Preview" variant="outline" />
<Button title="Deleting" variant="danger" loading /> import { PlButton } from '@pelagic-ui/angular';
@Component({
imports: [PlButton],
template: `
<button plButton>Save</button>
<button plButton variant="outline">Preview</button>
<button plButton variant="danger" [loading]="true">Deleting</button>`,
})
export class Actions {} Input Link to Input
A labelled text field with hint and error messages wired to aria-describedby.
Code
const [email, setEmail] = useState('');
<Input label="Email" type="email" value={email}
onChange={(e) => setEmail(e.target.value)}
error={email ? undefined : 'Required'} /> <Input v-model="email" label="Email" type="email"
:error="email ? undefined : 'Required'" /> <Input bind:value={email} label="Email" type="email"
error={email ? undefined : 'Required'} /> <Input name="email" label="Email" type="email" hint="We never share it" /> <Input label="Email" value={email} onChangeText={setEmail}
keyboardType="email-address" error={email ? undefined : 'Required'} /> <pl-input label="Email" type="email" [(value)]="email"
[error]="email() ? undefined : 'Required'" /> Textarea Link to Textarea
Multi-line text with the same label, hint and error wiring as Input.
Code
<Textarea label="Project brief" rows={4} value={brief}
onChange={(e) => setBrief(e.target.value)} hint="Markdown supported" /> <Textarea v-model="brief" label="Project brief" :rows="4" hint="Markdown supported" /> <Textarea bind:value={brief} label="Project brief" rows={4} hint="Markdown supported" /> <Textarea name="brief" label="Project brief" rows={4} hint="Markdown supported" /> <Textarea label="Project brief" value={brief} onChangeText={setBrief} /> <pl-textarea label="Project brief" [rows]="4" [(value)]="brief" hint="Markdown supported" /> Select Link to Select
A native select with a styled trigger, placeholder option and linked label.
Selected: none
Code
<Select label="Team" placeholder="Choose a team" value={team}
onChange={(e) => setTeam(e.target.value)}
options={[{ label: 'Design', value: 'design' }, { label: 'Engineering', value: 'eng' }]} /> <Select v-model="team" label="Team" placeholder="Choose a team" :options="teams" /> <Select bind:value={team} label="Team" placeholder="Choose a team" options={teams} /> <Select name="team" label="Team" placeholder="Choose a team" options={teams} /> <Select label="Team" value={team} onChange={setTeam} options={teams} /> <pl-select label="Team" placeholder="Choose a team" [options]="teams" [(value)]="team" /> Checkbox Link to Checkbox
A native checkbox with a visible, clickable label.
Code
<Checkbox label="Email me updates" checked={updates}
onChange={(e) => setUpdates(e.target.checked)} /> <Checkbox v-model="updates" label="Email me updates" /> <Checkbox bind:checked={updates} label="Email me updates" /> <Checkbox name="updates" label="Email me updates" checked /> <Checkbox label="Email me updates" checked={updates} onChange={setUpdates} /> <pl-checkbox label="Email me updates" [(value)]="updates" /> Switch Link to Switch
An on/off toggle using role="switch" with aria-checked.
Dark previews: On
Code
<Switch label="Dark previews" checked={dark} onChange={setDark} /> <Switch v-model="dark" label="Dark previews" /> <Switch bind:checked={dark} label="Dark previews" /> <Switch name="dark" label="Dark previews" checked /> <Switch label="Dark previews" checked={dark} onChange={setDark} /> <pl-switch label="Dark previews" [(value)]="dark" /> RadioGroup Link to RadioGroup
A fieldset of radio buttons with a legend; one option can be chosen.
Plan: pro
Code
<RadioGroup name="plan" label="Plan" value={plan} onChange={setPlan}
options={[{ label: 'Free', value: 'free' }, { label: 'Pro', value: 'pro' }]} /> <RadioGroup v-model="plan" name="plan" label="Plan" :options="plans" /> <RadioGroup bind:value={plan} name="plan" label="Plan" options={plans} /> <RadioGroup name="plan" label="Plan" value="pro" options={plans} /> <RadioGroup label="Plan" value={plan} onChange={setPlan} options={plans} /> <pl-radio-group name="plan" label="Plan" [options]="plans" [(value)]="plan" /> Slider Link to Slider
A range input with an optional live value readout.
Code
<Slider label="Budget" value={budget} onChange={setBudget} showValue /> <Slider v-model="budget" label="Budget" show-value /> <Slider bind:value={budget} label="Budget" showValue /> <Slider name="budget" label="Budget" value={40} showValue /> <pl-slider label="Budget" [(value)]="budget" showValue /> Segmented Link to Segmented
A compact single-choice control for switching views.
View: grid
Code
<Segmented label="View" value={view} onChange={setView}
options={[{ label: 'Grid', value: 'grid' }, { label: 'List', value: 'list' }]} /> <Segmented v-model="view" label="View" :options="views" /> <Segmented bind:value={view} label="View" options={views} /> <Segmented name="view" label="View" options={views} /> <Segmented label="View" value={view} onChange={setView} options={views} /> <pl-segmented label="View" [options]="views" [(value)]="view" />