Input group
ProA text field with things joined to it in one bordered row: a prefix or suffix (a currency, a unit, a domain, an icon), and a count against a limit. Put an Input (or a Textarea) inside with InputGroupAddons at its start or end and an optional InputGroupCount; the group draws the border and the focus ring, and the field inside draws neither. It goes inside a Field like the input it holds.
An addon that names something the field needs to be understood ("per month", "@company.com") sets describes: it is read with the field, joined to its aria-describedby. One that only decorates (an icon) is hidden from screen readers.
Version 5.2.0· other versions
This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).
Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.8.0, 2.9.0, 2.10.0, 3.0.0, 4.0.0, 4.0.1, 4.0.2, 4.1.0, 4.2.0 and 4.3.0: the page came later, or had another name then.
Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.
Preview
Loading the preview
Install
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/input-group
Brings in: locale, motion, tokens, use-announcer, utils
Usage
"use client"
import * as React from "react"
import { AtSign } from "lucide-react"
import { Field } from "@/components/candor/ui/field"
import { Input } from "@/components/candor/ui/input"
import { Textarea } from "@/components/candor/ui/textarea"
import { InputGroup, InputGroupAddon, InputGroupCount } from "@/components/candor/ui/input-group"
// Addons that name something the field needs ("per month") are read with it; an icon is decoration. The count is
// announced only at 80% of the limit, at the limit and over it.
export default function Example() {
const [reply, setReply] = React.useState("")
return (
<div className="flex w-full max-w-sm flex-col gap-4">
<Field label="Budget">
<InputGroup>
<InputGroupAddon>$</InputGroupAddon>
<Input inputMode="decimal" />
<InputGroupAddon align="end" describes>
per month
</InputGroupAddon>
</InputGroup>
</Field>
<Field label="Handle">
<InputGroup>
<InputGroupAddon>
<AtSign />
</InputGroupAddon>
<Input autoComplete="username" />
</InputGroup>
</Field>
<Field label="Reply to the customer">
<InputGroup className="items-end">
<Textarea value={reply} onChange={(e) => setReply(e.target.value)} />
<InputGroupCount value={reply} max={280} className="pb-2" />
</InputGroup>
</Field>
</div>
)
}<InputGroup>
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | — | Classes for the bordered row. |
| childrenrequired | React.ReactNode | — | An `Input` or `Textarea`, with `InputGroupAddon`s and an `InputGroupCount` beside it. |
| id | string | undefined | — | Field's wiring, passed on to the field inside: its id. |
| "aria-describedby" | string | undefined | — | Field's wiring, passed on to the field inside, before its own description. |
| "aria-invalid" | boolean | "true" | "false" | undefined | — | Field's wiring, passed on to the field inside. |
| "aria-required" | boolean | "true" | "false" | undefined | — | Field's wiring, passed on to the field inside. |
| "data-trust" | string | undefined | — | Field's trust state, passed on to the field inside. |
| attend | boolean | undefined | — | Field's ring after a failed submit, passed on to the field inside. |
| onAnimationEnd | React.AnimationEventHandler | undefined | — | Field's handler that clears the ring, passed on to the field inside. |
<InputGroupAddon>
Text or an icon joined to a field in an InputGroup: "
align="end" puts it after the field.| Prop | Type | Default | Description |
|---|---|---|---|
| align | "start" | "end" | undefined | "start" | Which side of the field it sits on, in the reading direction. |
| describes | boolean | undefined | false | It names something the field needs to be understood ("per month", "@company.com"): read with the field, joined to its description. Left out, it is decoration and hidden from screen readers. |
| className | string | undefined | — | Classes, merged with Candor's. |
| childrenrequired | React.ReactNode | — | The addon's text or icon. |
<InputGroupCount>
The field's length against a limit, "12 / 280", at the end of an InputGroup (or under a field). Counts characters as a person sees them (grapheme clusters, so an emoji is one). Screen readers hear it only when it matters: once at 80% of the limit ("56 characters left"), at the limit, and when it goes over, each once as the count crosses into it, never per key. Past the limit it turns the destructive colour; whether that stops a submit is the app's.
| Prop | Type | Default | Description |
|---|---|---|---|
| valuerequired | string | — | The field's text, which it counts. |
| max | number | undefined | — | The limit. Left out, it shows the count alone and announces nothing. |
| className | string | undefined | — | Classes, merged with Candor's. |
Strings
The fixed words, in English by default. Change any of them under inputGroup in <LocaleProvider strings={{ … }}>; the table is exported as inputGroupStrings.
| Key | English |
|---|---|
| countOf | {count} / {max} |
| near | { one: "{left} character left", other: "{left} characters left" } |
| full | Limit reached: {max} characters |
| over | { one: "{over} character over the limit", other: "{over} characters over the limit" } |
Motion
| Moment | Family | What it does |
|---|---|---|
| count | value, fade | The count crossfades when it changes, at most once a second, so a burst of typing plays it once, not per key. Its text is always the current count. |
| prefix | arrive, rise | A start addon that appears after the group first drew rises in. |
| suffix | pop, scale | An end addon that appears after the group first drew fades in. |
Set with --cd-motion-input-group-<moment>-* in your brand file; the families are in the design reference.