Forms
Text fields, choices, pickers, and the other controls that collect input from the user.
Input
Input is a single-line text field. Pass label to name it, and helperText or error to show a message below it.
Pick a size from xs to lg.
import { useState } from "react";
import { Input } from "@manta-eu/react/input";
const [title, setTitle] = useState("Write the release notes");
<Input
label="Title"
placeholder="What needs doing?"
value={title}
onChange={(event) => setTitle(event.target.value)}
error={title.trim() === "" ? "Give the todo a title" : undefined}
/>;
Textarea
Textarea is a multi-line text field with the same label and error props as Input. Set maxLength with
showCharacterCount to show a counter, and minRows or maxRows to bound its height.
import { useState } from "react";
import { Textarea } from "@manta-eu/react/input";
const [notes, setNotes] = useState("Cover the new design system pages.");
<Textarea
label="Notes"
placeholder="Add details"
value={notes}
onChange={(event) => setNotes(event.target.value)}
maxLength={200}
showCharacterCount
/>;
InputNumeric
InputNumeric is a number field with stepper buttons. It is controlled: pass value and onValueChange. Set min,
max, and step to bound the value. The arrow keys step it too.
import { useState } from "react";
import { InputNumeric } from "@manta-eu/react/input";
const [estimate, setEstimate] = useState(2);
<InputNumeric
value={estimate}
onValueChange={setEstimate}
min={0}
max={40}
step={0.5}
aria-label="Estimate in hours"
/>;
InputTags
InputTags turns typed text into removable chips. The user presses Enter or a comma to add a tag. It is controlled:
pass a value array and onChange.
import { useState } from "react";
import { InputTags } from "@manta-eu/react/input";
const [tags, setTags] = useState(["docs", "release"]);
<InputTags value={tags} onChange={setTags} placeholder="Add a tag…" aria-label="Tags" />;
InputOTP
InputOTP collects a one-time code, one character per slot. Set maxLength and render one InputOTPSlot per
character inside an InputOTPGroup. Add InputOTPSeparator to split the slots.
import { useState } from "react";
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@manta-eu/react/input-otp";
const [code, setCode] = useState("");
<InputOTP maxLength={6} value={code} onChange={setCode} aria-label="Verification code">
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>;
Checkbox
Checkbox toggles one option on or off. Pass label to name it, and checked with onCheckedChange to control it.
Set checked to "indeterminate" for a partial selection.
import { useState } from "react";
import { Checkbox } from "@manta-eu/react/checkbox";
const [done, setDone] = useState(true);
<Checkbox label="Write the release notes" checked={done} onCheckedChange={(checked) => setDone(checked === true)} />;
Radio
RadioGroup lets the user pick one option from a short list. Give each RadioGroupItem a value and a label. Set
size="large" for a bordered card with an icon and a description.
import { useState } from "react";
import { RadioGroup, RadioGroupItem } from "@manta-eu/react/radio";
const [priority, setPriority] = useState("medium");
<RadioGroup value={priority} onValueChange={setPriority} aria-label="Priority">
<RadioGroupItem value="low" label="Low" />
<RadioGroupItem value="medium" label="Medium" />
<RadioGroupItem value="high" label="High" />
</RadioGroup>;
Switch
Switch turns a setting on or off right away. Pass checked and onCheckedChange, and add label and description
to explain the setting.
Send an email the day before a todo is due.
import { useState } from "react";
import { Switch } from "@manta-eu/react/switch";
const [reminders, setReminders] = useState(true);
<Switch
label="Due date reminders"
description="Send an email the day before a todo is due."
checked={reminders}
onCheckedChange={setReminders}
/>;
Select
Select picks one value from a dropdown list. SelectTrigger needs an id and a label; add hiddenLabel to hide the
label visually. Put one SelectItem per option inside SelectContent.
import { useState } from "react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@manta-eu/react/select";
const [status, setStatus] = useState("in-progress");
<Select value={status} onValueChange={setStatus}>
<SelectTrigger id="todo-status" label="Status" className="w-[180px]">
<SelectValue placeholder="Pick a status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="todo">To do</SelectItem>
<SelectItem value="in-progress">In progress</SelectItem>
<SelectItem value="done">Done</SelectItem>
</SelectContent>
</Select>;
Combobox
Combobox is a popover with a searchable list. Wrap any trigger in ComboboxTrigger, then fill ComboboxContent with
a ComboboxInput and a ComboboxList of ComboboxItems. An item closes the popover on select unless you set
closeOnSelect={false}.
import { useState } from "react";
import { Button } from "@manta-eu/react/button";
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxList,
ComboboxTrigger,
} from "@manta-eu/react/combobox";
const assignees = [
{ value: "ana", label: "Ana Horvat" },
{ value: "marko", label: "Marko Petrović" },
{ value: "sara", label: "Sara Novak" },
];
const [assignee, setAssignee] = useState<string>();
const selected = assignees.find((person) => person.value === assignee);
<Combobox>
<ComboboxTrigger asChild>
<Button variant="tertiary" className="min-w-[180px]">
{selected?.label ?? "Assign to…"}
</Button>
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput placeholder="Search people…" />
<ComboboxList>
<ComboboxEmpty>No one found.</ComboboxEmpty>
{assignees.map((person) => (
<ComboboxItem
key={person.value}
value={person.label}
selected={assignee === person.value}
onSelect={() => setAssignee(person.value)}
>
{person.label}
</ComboboxItem>
))}
</ComboboxList>
</ComboboxContent>
</Combobox>;
MultiSelect
MultiSelect picks several values from a searchable list and shows them as chips. Pass options, a value array,
and onChange. Add clearable for a button that removes every value.
import { useState } from "react";
import { MultiSelect } from "@manta-eu/react/multi-select";
const labels = [
{ value: "bug", label: "Bug" },
{ value: "docs", label: "Docs" },
{ value: "design", label: "Design" },
{ value: "backend", label: "Backend" },
];
const [selected, setSelected] = useState(["docs"]);
<MultiSelect
options={labels}
value={selected}
onChange={setSelected}
placeholder="Add labels"
clearable
aria-label="Labels"
className="w-80"
/>;
Slider
Slider picks a number by dragging a thumb along a track. Its value is an array: pass two numbers for a range. Use
thumbProps to give each thumb an aria-label.
40% done
import { useState } from "react";
import { Slider } from "@manta-eu/react/slider";
const [progress, setProgress] = useState([40]);
<div className="flex w-80 flex-col gap-2">
<Slider value={progress} onValueChange={setProgress} step={10} thumbProps={[{ "aria-label": "Progress" }]} />
<p className="typography-caption text-muted-foreground">{progress[0]}% done</p>
</div>;
Calendar
Calendar shows a month grid for picking dates. It wraps react-day-picker: set mode to single, multiple, or
range, and pass selected with onSelect.
| Mo | Tu | We | Th | Fr | Sa | Su |
|---|---|---|---|---|---|---|
import { useState } from "react";
import { Calendar } from "@manta-eu/react/calendar";
const [dueDate, setDueDate] = useState<Date | undefined>(new Date(2026, 10, 14));
<Calendar mode="single" selected={dueDate} onSelect={setDueDate} defaultMonth={dueDate} />;