Klarhimmel UI

Popover

Stable

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/button

When 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

PropTypeDefaultDescription
PopoverTrigger asChildReactNode-Wrap the Button that toggles the panel.
PopoverContent labelstring-Required. The accessible name of the panel.
PopoverContent align'start' | 'center' | 'end''end'Alignment to the trigger; it flips to stay on screen.
PopoverClose asChildReactNode-Wrap a button inside the panel that closes it.
open / defaultOpen / onOpenChangeboolean / (open: boolean) => void-On Popover. Controlled or uncontrolled state.
classNamestring-Extra classes, merged after the component's own.

Accessibility

KeyDoes
Enter / SpaceOpens from the trigger
TabMoves through the panel
EscapeCloses and returns focus to the trigger

A click or tap outside the panel also closes it.

Accessibility