HextaUI

Input group

An input with icons, text, buttons or a keyboard hint attached, sharing one border and focus ring.

"use client"

import * as React from "react"
import { IconArrowUp, IconLock, IconSearch } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupClear,
  InputGroupCount,
  InputGroupInput,
  InputGroupPasswordToggle,
  InputGroupTextarea,
} from "@/components/ui/input-group"

export function InputGroupDemo() {
  const [message, setMessage] = React.useState("")

  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <InputGroup>
        <InputGroupInput
          type="search"
          defaultValue="Accordion"
          placeholder="Search components"
          aria-label="Search components"
        />
        <InputGroupAddon>
          <IconSearch />
        </InputGroupAddon>
        <InputGroupAddon align="inline-end">
          <InputGroupClear />
        </InputGroupAddon>
      </InputGroup>
      <InputGroup>
        <InputGroupInput
          type="password"
          defaultValue="hunter2-but-longer"
          autoComplete="current-password"
          aria-label="Password"
        />
        <InputGroupAddon>
          <IconLock />
        </InputGroupAddon>
        <InputGroupAddon align="inline-end">
          <InputGroupPasswordToggle />
        </InputGroupAddon>
      </InputGroup>
      <InputGroup>
        <InputGroupTextarea
          value={message}
          onChange={(event) => setMessage(event.target.value)}
          maxLength={140}
          placeholder="Leave a note for the team"
          aria-label="Note"
        />
        <InputGroupAddon align="block-end">
          <InputGroupCount />
          <InputGroupButton
            variant="default"
            size="icon-xs"
            aria-label="Post note"
            disabled={!message.trim()}
            className="ms-auto"
          >
            <IconArrowUp />
          </InputGroupButton>
        </InputGroupAddon>
      </InputGroup>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/input-group.json

Adds the component, the HextaUI theme tokens and any HextaUI components it depends on.

import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupClear,
  InputGroupCount,
  InputGroupInput,
  InputGroupPasswordToggle,
  InputGroupText,
  InputGroupTextarea,
} from "@/components/ui/input-group"
<InputGroup>
  <InputGroupInput placeholder="Search…" aria-label="Search" />
  <InputGroupAddon>
    <IconSearch />
  </InputGroupAddon>
  <InputGroupAddon align="inline-end">
    <InputGroupClear />
  </InputGroupAddon>
</InputGroup>

Put <InputGroupInput /> or <InputGroupTextarea /> first and the addons after it. Addons place themselves with align, so the field comes first in the tab order and for screen readers.

The group tracks its field, so the smart parts need no wiring. <InputGroupClear />, <InputGroupPasswordToggle /> and <InputGroupCount /> read the value, type and maxLength from the field, whether it's controlled or not.

InputGroup
├── InputGroupAddon
│   ├── InputGroupText
│   ├── InputGroupButton
│   ├── InputGroupClear
│   ├── InputGroupCount
│   └── InputGroupPasswordToggle
└── InputGroupInput / InputGroupTextarea

Icon

Icons sit inside the border on either side. Clicking an icon focuses the input, so the whole group feels like one field.

import { IconCheck, IconMail, IconSearch } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
} from "@/components/ui/input-group"

export function InputGroupIcon() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <InputGroup>
        <InputGroupInput placeholder="Search…" aria-label="Search" />
        <InputGroupAddon>
          <IconSearch />
        </InputGroupAddon>
      </InputGroup>
      <InputGroup>
        <InputGroupInput
          type="email"
          defaultValue="[email protected]"
          aria-label="Email"
        />
        <InputGroupAddon>
          <IconMail />
        </InputGroupAddon>
        <InputGroupAddon align="inline-end">
          <IconCheck className="text-success" aria-label="Verified" />
        </InputGroupAddon>
      </InputGroup>
    </div>
  )
}

Clear

<InputGroupClear /> fades in once there's a value. It clears through the browser's edit history, so Cmd+Z brings the text back, and it fires your onChange. Escape clears too. A second Escape is left for the dialog or popover around it.

Apple, Apricot

"use client"

import * as React from "react"
import { IconSearch } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupClear,
  InputGroupInput,
} from "@/components/ui/input-group"

const fruits = ["Apple", "Apricot", "Banana", "Blackberry", "Cherry", "Fig"]

export function InputGroupClearDemo() {
  const [query, setQuery] = React.useState("ap")
  const matches = fruits.filter((fruit) =>
    fruit.toLowerCase().includes(query.trim().toLowerCase())
  )

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <InputGroup>
        <InputGroupInput
          type="search"
          value={query}
          onChange={(event) => setQuery(event.target.value)}
          placeholder="Filter fruit"
          aria-label="Filter fruit"
        />
        <InputGroupAddon>
          <IconSearch />
        </InputGroupAddon>
        <InputGroupAddon align="inline-end">
          <InputGroupClear />
        </InputGroupAddon>
      </InputGroup>
      <p className="text-sm text-muted-foreground">
        {matches.length > 0 ? matches.join(", ") : "No fruit matches."}
      </p>
    </div>
  )
}

Character count

<InputGroupCount /> counts against the field's maxLength. Only the digits that change roll. The count darkens near the limit and turns red at it, and a typed key past the limit nudges it. Screen readers hear a message when the field gets close to the limit and when it reaches it, never on every key.

import { IconAt } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupCount,
  InputGroupInput,
} from "@/components/ui/input-group"

export function InputGroupCountDemo() {
  return (
    <InputGroup className="max-w-sm">
      <InputGroupInput
        defaultValue="hextaui"
        maxLength={15}
        autoComplete="username"
        aria-label="Username"
      />
      <InputGroupAddon>
        <IconAt />
      </InputGroupAddon>
      <InputGroupAddon align="inline-end">
        <InputGroupCount />
      </InputGroupAddon>
    </InputGroup>
  )
}

Text

Use <InputGroupText /> for units, currencies and URL parts. The input's padding shrinks next to an addon so the text reads as one value.

$
USD
https://
.com
import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
  InputGroupText,
} from "@/components/ui/input-group"

export function InputGroupTextDemo() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <InputGroup>
        <InputGroupInput
          placeholder="0.00"
          inputMode="decimal"
          aria-label="Price"
        />
        <InputGroupAddon>
          <InputGroupText>$</InputGroupText>
        </InputGroupAddon>
        <InputGroupAddon align="inline-end">
          <InputGroupText>USD</InputGroupText>
        </InputGroupAddon>
      </InputGroup>
      <InputGroup>
        <InputGroupInput placeholder="hextaui" aria-label="Domain" />
        <InputGroupAddon>
          <InputGroupText>https://</InputGroupText>
        </InputGroupAddon>
        <InputGroupAddon align="inline-end">
          <InputGroupText>.com</InputGroupText>
        </InputGroupAddon>
      </InputGroup>
    </div>
  )
}

Button

<InputGroupButton /> is a ghost button sized to fit inside the field. Its corners are concentric with the group's, and it gets its own focus ring.

"use client"

import * as React from "react"
import { IconCheck, IconCopy, IconX } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
} from "@/components/ui/input-group"

export function InputGroupButtonDemo() {
  const [copied, setCopied] = React.useState(false)
  const [query, setQuery] = React.useState("button group")

  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <InputGroup>
        <InputGroupInput
          readOnly
          defaultValue="https://hextaui.com/docs/input-group"
          aria-label="Share link"
        />
        <InputGroupAddon align="inline-end">
          <InputGroupButton
            size="icon-xs"
            aria-label={copied ? "Copied" : "Copy link"}
            onClick={async () => {
              await navigator.clipboard
                ?.writeText("https://hextaui.com/docs/input-group")
                .catch(() => {})
              setCopied(true)
              setTimeout(() => setCopied(false), 1500)
            }}
          >
            {copied ? <IconCheck /> : <IconCopy />}
          </InputGroupButton>
        </InputGroupAddon>
      </InputGroup>
      <InputGroup>
        <InputGroupInput
          value={query}
          onChange={(event) => setQuery(event.target.value)}
          placeholder="Search components…"
          aria-label="Search components"
        />
        <InputGroupAddon align="inline-end">
          {query ? (
            <InputGroupButton
              size="icon-xs"
              aria-label="Clear search"
              onClick={() => setQuery("")}
            >
              <IconX />
            </InputGroupButton>
          ) : null}
          <InputGroupButton variant="secondary">Search</InputGroupButton>
        </InputGroupAddon>
      </InputGroup>
    </div>
  )
}

Keyboard hint

A plain <kbd> inside an addon is styled as a key cap. It's a visual hint only, so bind the shortcut yourself.

⌘K
/
import { IconSearch } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
} from "@/components/ui/input-group"

export function InputGroupKbd() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <InputGroup>
        <InputGroupInput placeholder="Search docs…" aria-label="Search docs" />
        <InputGroupAddon>
          <IconSearch />
        </InputGroupAddon>
        <InputGroupAddon align="inline-end">
          <kbd>⌘</kbd>
          <kbd>K</kbd>
        </InputGroupAddon>
      </InputGroup>
      <InputGroup>
        <InputGroupInput
          placeholder="Jump to file…"
          aria-label="Jump to file"
        />
        <InputGroupAddon align="inline-end">
          <kbd>/</kbd>
        </InputGroupAddon>
      </InputGroup>
    </div>
  )
}

Textarea

<InputGroupTextarea /> grows with its content up to 16rem, then scrolls. The height eases between lines instead of jumping. A block-end addon becomes a toolbar under it, and buttons at its edges get the same inset as the corner they sit in.

"use client"

import * as React from "react"
import { IconArrowUp, IconPaperclip } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupCount,
  InputGroupTextarea,
} from "@/components/ui/input-group"

export function InputGroupTextareaDemo() {
  const [value, setValue] = React.useState("")

  return (
    <InputGroup className="max-w-sm">
      <InputGroupTextarea
        value={value}
        onChange={(event) => setValue(event.target.value)}
        maxLength={280}
        placeholder="Write a reply…"
        aria-label="Reply"
      />
      <InputGroupAddon align="block-end">
        <InputGroupButton size="icon-xs" aria-label="Attach a file">
          <IconPaperclip />
        </InputGroupButton>
        <InputGroupCount className="ms-auto" />
        <InputGroupButton
          variant="default"
          size="icon-xs"
          aria-label="Send"
          disabled={!value.trim()}
        >
          <IconArrowUp />
        </InputGroupButton>
      </InputGroupAddon>
    </InputGroup>
  )
}

A block-start addon sits above the field. Add separator to draw a hairline between them.

script.js
import { IconCopy, IconFileCode } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupText,
  InputGroupTextarea,
} from "@/components/ui/input-group"

export function InputGroupBlockStart() {
  return (
    <InputGroup className="max-w-sm">
      <InputGroupTextarea
        defaultValue={'console.log("Hello from HextaUI")'}
        aria-label="script.js"
      />
      <InputGroupAddon align="block-start" separator>
        <InputGroupText>
          <IconFileCode />
          script.js
        </InputGroupText>
        <InputGroupButton size="icon-xs" aria-label="Copy" className="ms-auto">
          <IconCopy />
        </InputGroupButton>
      </InputGroupAddon>
    </InputGroup>
  )
}

Password

<InputGroupPasswordToggle /> switches a type="password" field to text and back. The caret and selection stay where they were, a mouse click keeps focus in the field, and the password is hidden again when the form submits. Control it with revealed.

import { IconLock } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
  InputGroupPasswordToggle,
} from "@/components/ui/input-group"

export function InputGroupPassword() {
  return (
    <InputGroup className="max-w-sm">
      <InputGroupInput
        type="password"
        defaultValue="correct horse battery staple"
        autoComplete="current-password"
        aria-label="Password"
      />
      <InputGroupAddon>
        <IconLock />
      </InputGroupAddon>
      <InputGroupAddon align="inline-end">
        <InputGroupPasswordToggle />
      </InputGroupAddon>
    </InputGroup>
  )
}

Sizes

size on the group sets the height and passes down to the input, matching <Input /> sizes. Buttons keep concentric corners at every size.

import { IconSearch } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
} from "@/components/ui/input-group"

export function InputGroupSizes() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      {(["sm", "default", "lg"] as const).map((size) => (
        <InputGroup key={size} size={size}>
          <InputGroupInput
            placeholder={`Size ${size}`}
            aria-label={`Size ${size}`}
          />
          <InputGroupAddon>
            <IconSearch />
          </InputGroupAddon>
          <InputGroupAddon align="inline-end">
            <InputGroupButton>Go</InputGroupButton>
          </InputGroupAddon>
        </InputGroup>
      ))}
    </div>
  )
}

Invalid

Set aria-invalid on the input and the whole group turns red, including its focus ring. Link the message with aria-describedby.

Enter a complete email address.

import { IconAt, IconAlertCircle } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
} from "@/components/ui/input-group"

export function InputGroupInvalid() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <InputGroup>
        <InputGroupInput
          defaultValue="preet@"
          aria-label="Email"
          aria-invalid
          aria-describedby="email-error"
        />
        <InputGroupAddon>
          <IconAt />
        </InputGroupAddon>
        <InputGroupAddon align="inline-end">
          <IconAlertCircle className="text-destructive" aria-hidden />
        </InputGroupAddon>
      </InputGroup>
      <p id="email-error" className="text-sm text-destructive">
        Enter a complete email address.
      </p>
    </div>
  )
}

Disabled

A disabled input dims the whole group and shows a not-allowed cursor over it. Disable addon buttons too, since they stay usable otherwise.

import { IconLink } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
} from "@/components/ui/input-group"

export function InputGroupDisabled() {
  return (
    <InputGroup className="max-w-sm">
      <InputGroupInput
        disabled
        defaultValue="https://hextaui.com"
        aria-label="Link"
      />
      <InputGroupAddon>
        <IconLink />
      </InputGroupAddon>
      <InputGroupAddon align="inline-end">
        <InputGroupButton disabled>Copy</InputGroupButton>
      </InputGroupAddon>
    </InputGroup>
  )
}

Loading

Inline addons ease to their new width when their content changes, so the field never jumps as a spinner turns into a result count. The spinner only turns when motion is allowed, and role="status" announces the text.

5 packages
"use client"

import * as React from "react"
import { IconLoader2, IconSearch } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
  InputGroupText,
} from "@/components/ui/input-group"

const packages = ["react", "react-dom", "react-hook-form", "redux", "remix"]

export function InputGroupLoading() {
  const [query, setQuery] = React.useState("re")
  const [pending, setPending] = React.useState(false)
  const timer = React.useRef<ReturnType<typeof setTimeout>>(undefined)
  const results = packages.filter((name) => name.includes(query.trim()))

  React.useEffect(() => () => clearTimeout(timer.current), [])

  return (
    <InputGroup className="max-w-sm">
      <InputGroupInput
        value={query}
        onChange={(event) => {
          setQuery(event.target.value)
          setPending(true)
          clearTimeout(timer.current)
          timer.current = setTimeout(() => setPending(false), 600)
        }}
        aria-label="Search packages"
        aria-busy={pending}
      />
      <InputGroupAddon>
        <IconSearch />
      </InputGroupAddon>
      <InputGroupAddon align="inline-end">
        <InputGroupText role="status">
          {pending ? (
            <>
              <IconLoader2 className="motion-safe:animate-spin" aria-hidden />
              Searching
            </>
          ) : results.length === 1 ? (
            "1 package"
          ) : (
            `${results.length} packages`
          )}
        </InputGroupText>
      </InputGroupAddon>
    </InputGroup>
  )
}

Render an <InputGroupButton /> as a dropdown trigger to scope the input.

import { IconChevronDown } from "@tabler/icons-react"

import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
} from "@/components/ui/input-group"

export function InputGroupDropdown() {
  return (
    <InputGroup className="max-w-sm">
      <InputGroupInput placeholder="Enter a search term" aria-label="Search" />
      <InputGroupAddon align="inline-end">
        <DropdownMenu>
          <DropdownMenuTrigger render={<InputGroupButton />}>
            Everywhere
            <IconChevronDown data-icon="inline-end" />
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end">
            <DropdownMenuGroup>
              <DropdownMenuItem>Everywhere</DropdownMenuItem>
              <DropdownMenuItem>This project</DropdownMenuItem>
              <DropdownMenuItem>Documentation</DropdownMenuItem>
            </DropdownMenuGroup>
          </DropdownMenuContent>
        </DropdownMenu>
      </InputGroupAddon>
    </InputGroup>
  )
}

Long content

Long values scroll inside the input instead of stretching the group. Wrap long addon text in a truncating span with a max width.

.example.com
https://workspace-with-a-long-name.
import { IconWorld } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
  InputGroupText,
} from "@/components/ui/input-group"

export function InputGroupLongContent() {
  return (
    <div className="flex w-72 flex-col gap-4">
      <InputGroup>
        <InputGroupInput
          defaultValue="averyveryverylongunbrokenvaluethatkeepsgoingandgoing"
          aria-label="Value"
        />
        <InputGroupAddon>
          <IconWorld />
        </InputGroupAddon>
        <InputGroupAddon align="inline-end">
          <InputGroupText>.example.com</InputGroupText>
        </InputGroupAddon>
      </InputGroup>
      <InputGroup>
        <InputGroupInput placeholder="name" aria-label="Subdomain" />
        <InputGroupAddon>
          <InputGroupText className="max-w-28">
            <span className="truncate">
              https://workspace-with-a-long-name.
            </span>
          </InputGroupText>
        </InputGroupAddon>
      </InputGroup>
    </div>
  )
}

Right to left

Addons, padding and corner radii use logical sides, so inline-start lands on the right.

⌘K
ر.س
import { IconArrowUp, IconSearch } from "@tabler/icons-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
  InputGroupText,
} from "@/components/ui/input-group"

export function InputGroupRtl() {
  return (
    <div dir="rtl" className="flex w-full max-w-sm flex-col gap-4">
      <InputGroup>
        <InputGroupInput placeholder="ابحث…" aria-label="بحث" />
        <InputGroupAddon>
          <IconSearch />
        </InputGroupAddon>
        <InputGroupAddon align="inline-end">
          <kbd>⌘K</kbd>
        </InputGroupAddon>
      </InputGroup>
      <InputGroup>
        <InputGroupInput placeholder="0.00" aria-label="السعر" />
        <InputGroupAddon>
          <InputGroupText>ر.س</InputGroupText>
        </InputGroupAddon>
        <InputGroupAddon align="inline-end">
          <InputGroupButton variant="default" size="icon-xs" aria-label="إرسال">
            <IconArrowUp />
          </InputGroupButton>
        </InputGroupAddon>
      </InputGroup>
    </div>
  )
}
KeyAction
TabMoves from the field to each addon button, in source order.
ShiftTabMoves back through the buttons and the field.
EscapeWith an InputGroupClear, clears the field. When it's already empty, Escape passes through.
  • Every field needs a name. Use a visible label, a Field, or aria-label. Icons and addon text aren't part of the field's name.
  • Give icon-only buttons an aria-label.
  • When addon text carries meaning, like a currency or a domain, add it to the label or reference it with aria-describedby.
  • <InputGroupClear /> is skipped in the tab order because Escape does the same thing. The password toggle stays tabbable and keeps the same name, with aria-pressed reporting its state.
  • When a submit finds the field invalid, the group shakes once. With reduced motion the red border is the only cue.
  • On touch screens the field text is at least 16px so phones don't zoom in when it's focused.

<InputGroupInput /> and <InputGroupTextarea /> accept the props of the elements they render. The other parts accept their element's attributes.

PropTypeDefault
sizeHeight of the group, passed down to the input.
"sm" | "default" | "lg""default"
AttributeDescription
data-slot="input-group"Target the group in CSS. Renders role="group".
data-sizeThe current size.
data-filledPresent while the field has a value.
data-shakePresent while the group shakes after a failed submit.
data-disabledSet it yourself to dim the group when only the addons are disabled.
--input-group-radiusCorner radius of the group. Buttons and key caps derive their radius from it.
--input-group-heightHeight of the group for the current size.
PropTypeDefault
sizeInherited from the group.
"sm" | "default" | "lg"–
aria-invalid
boolean–
disabled
booleanfalse
readOnly
booleanfalse
AttributeDescription
data-slot="input-group-control"Marks the field. The group reads its focus, invalid, disabled and read-only states from it.
data-invalidPresent when a surrounding Field marks the value invalid.
data-disabledPresent when the field is disabled.
data-focusedPresent while the field has focus.
data-filledPresent when the field has a value.
data-dirtyPresent once the value differs from the initial one.
data-touchedPresent once the field has been focused and left.
PropTypeDefault
autoResizeGrow with the content up to 16rem, easing between heights.
booleantrue
shakeShake the group when a submit finds it invalid.
booleantrue
rows
number–
aria-invalid
boolean–
disabled
booleanfalse
AttributeDescription
data-slot="input-group-control"Marks the field. The group reads its focus, invalid, disabled and read-only states from it.
data-invalidPresent when a surrounding Field marks the value invalid.
data-disabledPresent when the field is disabled.
data-focusedPresent while the field has focus.
data-filledPresent when the field has a value.
data-dirtyPresent once the value differs from the initial one.
data-touchedPresent once the field has been focused and left.
PropTypeDefault
align
"inline-start" | "inline-end" | "block-start" | "block-end""inline-start"
separatorDraw a hairline between a block addon and the field.
booleanfalse
AttributeDescription
data-slot="input-group-addon"Target addons in CSS.
data-alignThe current alignment.
data-separatorPresent when separator is set.
--input-group-addon-insetSpace between the group's edge and a button or key cap inside it.
PropTypeDefault
variant
"default" | "outline" | "secondary" | "ghost" | "destructive" | "link""ghost"
size
"xs" | "sm" | "icon-xs" | "icon-sm""xs"
type
string"button"
feedbackEvery Button prop works, including the loading and success flow.
booleanfalse
AttributeDescription
data-slot="input-group-button"Target addon buttons in CSS.
data-sizeThe current size.
PropTypeDefault
onClearCalled after the field is cleared.
() => void–
aria-label
string"Clear"
children
ReactNode<IconX />
AttributeDescription
data-slot="input-group-clear"Target the clear button in CSS.
data-visiblePresent while the field has a value and is editable.
PropTypeDefault
revealedControlled state. Leave unset to let it manage itself.
boolean–
onRevealedChange
(revealed: boolean) => void–
aria-label
string"Show password"
AttributeDescription
data-slot="input-group-password-toggle"Target the toggle in CSS.
data-revealedPresent while the password is shown.
PropTypeDefault
thresholdCharacters left when the count starts to stand out.
number10% of maxLength, at most 20
announcementMessage for screen readers when the count crosses the threshold or hits the limit.
(remaining: number) => string–
AttributeDescription
data-slot="input-group-count"Target the count in CSS.
data-state="near" | "limit"Present within the threshold, and when no characters are left.
data-bumpPresent briefly when a key is pressed at the limit.
AttributeDescription
data-slot="input-group-text"Target addon text in CSS.