- Components
Popover
A small floating panel next to its trigger, for extra detail or a tiny form. Built on Radix Popover.
npx shadcn add @klarhimmel/popover @klarhimmel/buttonWhen to use
Use for
Detail or a one-field form that belongs to a control.
Not for
A decision the person must make. Use a Dialog.
Pairs with
Button as the trigger, Field, PopoverClose.
Preview
popover.tsx
import { Theme } from '@/components/klarhimmel/theme/style';
import { Button } from '@/components/klarhimmel/ui/button';
import { Popover, PopoverTrigger, PopoverContent } from '@/components/klarhimmel/ui/popover';
export default function Example() {
return (
<Theme theme="brand">
<Popover>
<PopoverTrigger asChild><Button variant="secondary">Details</Button></PopoverTrigger>
<PopoverContent label="Cancellation policy">Free until 48 hours before arrival.</PopoverContent>
</Popover>
</Theme>
);
}Install
Import
import { Popover, PopoverTrigger, PopoverContent, PopoverClose } from '@/components/klarhimmel/ui/popover';- Lands in
- components/klarhimmel/ui/popover.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.
- Limit
- Overlays render into the Theme root with position: fixed. A transform on an ancestor re-parents fixed (a dialog centres in the wrapper) and overflow: hidden clips menus. Render <Theme> at the app root, outside any transformed wrapper.
Examples
Form
No note yet.
import { useState } from 'react'; import { Button } from '@/components/klarhimmel/ui/button'; import { Field, Input } from '@/components/klarhimmel/ui/input'; import { Popover, PopoverClose, PopoverContent, PopoverTrigger } from '@/components/klarhimmel/ui/popover'; export default function PopoverForm() { const [note, setNote] = useState(''); const [saved, setSaved] = useState(''); return ( <div className="flex min-h-72 flex-col items-start gap-3"> <Popover> <PopoverTrigger asChild><Button variant="secondary">Add a note</Button></PopoverTrigger> <PopoverContent label="Add a note" align="start"> <form className="flex flex-col gap-3" onSubmit={(e) => { e.preventDefault(); setSaved(note); }} > <Field label="Note for the housekeeping team">{(p) => <Input value={note} onChange={(e) => setNote(e.target.value)} {...p} />}</Field> <div className="flex justify-end gap-2"> <PopoverClose asChild><Button type="button" variant="ghost">Cancel</Button></PopoverClose> <PopoverClose asChild><Button type="submit">Save</Button></PopoverClose> </div> </form> </PopoverContent> </Popover> <p role="status" className="text-kh-small text-kh-muted">{saved ? `Saved: ${saved}` : 'No note yet.'}</p> </div> ); }Align
import { Button } from '@/components/klarhimmel/ui/button'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/klarhimmel/ui/popover'; const aligns = ['start', 'center', 'end'] as const; export default function PopoverAlign() { return ( <div className="flex min-h-12 flex-wrap items-start justify-between gap-3"> {aligns.map((align) => ( <Popover key={align}> <PopoverTrigger asChild><Button variant="secondary">Align {align}</Button></PopoverTrigger> <PopoverContent label={`Panel aligned to the ${align}`} align={align}> <p className="text-kh-small">Opens aligned to the {align} edge of its button, and flips to stay on screen.</p> </PopoverContent> </Popover> ))} </div> ); }
Anatomy
The parts and how they nest.
Popover
├─ PopoverTrigger (asChild)
└─ PopoverContent
└─ PopoverClose (asChild, optional)PopoverHolds the open state.PopoverTriggerWrap your Button with asChild; it gets aria-expanded.PopoverContentThe floating panel. Portals into the theme root so it keeps the tokens.PopoverCloseWrap a button inside the panel that should close it.
API reference
| Prop | Type | Default | Description |
|---|---|---|---|
PopoverTrigger asChild | ReactNode | - | Wrap the Button that toggles the panel. |
PopoverContent label | string | - | Required. The accessible name of the panel. |
PopoverContent align | 'start' | 'center' | 'end' | 'end' | Alignment to the trigger; it flips to stay on screen. |
PopoverClose asChild | ReactNode | - | Wrap a button inside the panel that closes it. |
open / defaultOpen / onOpenChange | boolean / (open: boolean) => void | - | On Popover. Controlled or uncontrolled state. |
className | string | - | Extra classes, merged after the component's own. |
Accessibility
| Key | Does |
|---|---|
Enter / Space | Opens from the trigger |
Tab | Moves through the panel |
Escape | Closes and returns focus to the trigger |
A click or tap outside the panel also closes it.
Accessibility