- Components
Input group
One field made of parts: a prefix or suffix, the input and a button, sharing one border and one focus ring.
npx shadcn add @klarhimmel/input-group @klarhimmel/inputWhen to use
Use for
A value with its unit, scheme or action beside it: kr, https://, a copy button, a search icon.
Not for
A plain field. Use Input. A choice from a list. Use Select.
Pairs with
Field, Kbd, Button.
Preview
krper night
Including VAT.
input-group.tsx
import { Theme } from '@/components/klarhimmel/theme/style';
import { Field } from '@/components/klarhimmel/ui/input';
import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/klarhimmel/ui/input-group';
export default function Example() {
return (
<Theme theme="brand">
<Field label="Price per night">
{(p) => (
<InputGroup>
<InputGroupAddon>kr</InputGroupAddon>
<InputGroupInput {...p} inputMode="numeric" />
</InputGroup>
)}
</Field>
</Theme>
);
}Install
Import
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupButton } from '@/components/klarhimmel/ui/input-group';- Lands in
- components/klarhimmel/ui/input-group.tsx, plus cn() and the theme item if they are missing. Import theme.css after Tailwind.
- Render in
- Render it inside <Theme>, once near the root of your app. Tokens and fonts are scoped to it, so outside it the component is unstyled.
Examples
Url
https://Share this address with guests.
import { useState } from 'react'; import { Check, Copy } from 'lucide-react'; import { Field } from '@/components/klarhimmel/ui/input'; import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@/components/klarhimmel/ui/input-group'; export default function InputGroupUrl() { const [copied, setCopied] = useState(false); return ( <div className="flex w-[min(24rem,calc(100cqw-3rem))] flex-col gap-3"> <Field label="Booking page address"> {(p) => ( <InputGroup> <InputGroupAddon>https://</InputGroupAddon> <InputGroupInput {...p} defaultValue="exempelhotellet.example/book" /> <InputGroupButton aria-label="Copy the address" onClick={() => setCopied(true)}> {copied ? <Check aria-hidden /> : <Copy aria-hidden />} </InputGroupButton> </InputGroup> )} </Field> <p role="status" className="text-kh-small text-kh-muted">{copied ? 'Copied.' : 'Share this address with guests.'}</p> </div> ); }States
/%That code has expired.
krimport { Search } from 'lucide-react'; import { Field } from '@/components/klarhimmel/ui/input'; import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/klarhimmel/ui/input-group'; import { Kbd } from '@/components/klarhimmel/ui/kbd'; export default function InputGroupStates() { return ( <div className="flex w-[min(24rem,calc(100cqw-3rem))] flex-col gap-5"> <Field label="Search rooms"> {(p) => ( <InputGroup> <InputGroupAddon><Search aria-hidden /></InputGroupAddon> <InputGroupInput {...p} placeholder="Sea view, balcony…" /> <InputGroupAddon align="end"><Kbd>/</Kbd></InputGroupAddon> </InputGroup> )} </Field> <Field label="Discount code" error="That code has expired."> {(p) => ( <InputGroup invalid> <InputGroupAddon>%</InputGroupAddon> <InputGroupInput {...p} defaultValue="SUMMER24" /> </InputGroup> )} </Field> <Field label="Currency (set by your plan)"> {(p) => ( <InputGroup disabled> <InputGroupAddon>kr</InputGroupAddon> <InputGroupInput {...p} defaultValue="SEK" disabled /> </InputGroup> )} </Field> </div> ); }
API reference
| Prop | Type | Default | Description |
|---|---|---|---|
InputGroup: invalid / disabled | boolean | false | Draws the red border or the disabled look on the whole group. Put aria-invalid or disabled on the input too. |
InputGroupAddon: align | 'start' | 'end' | 'start' | Which side of the input. Text is read in order, so write it as part of the question. An icon alone is decorative. |
InputGroupInput | <input> attributes | - | The input, without a frame of its own. Pass the Field's props through it. |
InputGroupButton | <button> attributes | type='button' | A button inside the frame. Give an icon-only one an aria-label. |
className | string | - | Extra classes, merged after the component's own. |
Accessibility
| Key | Does |
|---|---|
Tab | Moves through the input and the buttons in the group |
Enter / Space | Presses a focused button |
The group draws the frame and the focus ring; the input is named by its Field label. Text addons are read in order, so write them as part of the question.
Accessibility