Skip to content
Tier

Input group

Pro

A 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.

Markdown
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

With the Candor CLI

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

TSX · The example above
"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>

PropTypeDefaultDescription
classNamestring | undefined—Classes for the bordered row.
childrenrequiredReact.ReactNode—An `Input` or `Textarea`, with `InputGroupAddon`s and an `InputGroupCount` beside it.
idstring | 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.
attendboolean | undefined—Field's ring after a failed submit, passed on to the field inside.
onAnimationEndReact.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: "

quot;, "kg", "@company.com". At the start by default; align="end" puts it after the field.

PropTypeDefaultDescription
align"start" | "end" | undefined"start"Which side of the field it sits on, in the reading direction.
describesboolean | undefinedfalseIt 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.
classNamestring | undefined—Classes, merged with Candor's.
childrenrequiredReact.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.

PropTypeDefaultDescription
valuerequiredstring—The field's text, which it counts.
maxnumber | undefined—The limit. Left out, it shows the count alone and announces nothing.
classNamestring | 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.

KeyEnglish
countOf{count} / {max}
near{ one: "{left} character left", other: "{left} characters left" }
fullLimit reached: {max} characters
over{ one: "{over} character over the limit", other: "{over} characters over the limit" }

Motion

MomentFamilyWhat it does
countvalue, fadeThe 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.
prefixarrive, riseA start addon that appears after the group first drew rises in.
suffixpop, scaleAn 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.