- Components
Icon button
A button with only an icon. The label is its accessible name and its tooltip, so it can never be unnamed.
npx shadcn add @klarhimmel/icon-buttonWhen to use
Use for
Toolbars, table rows and tight corners: close, share, bold.
Not for
Anything a word would explain better. Use Button.
Pairs with
Tooltip (built in), Menu triggers, toolbars.
Preview
Ghost, soft, secondary, primary and disabled. Hover or tab to a button for its name.
icon-button.tsx
import { Bell } from 'lucide-react';
import { Theme } from '@/components/klarhimmel/theme/style';
import { IconButton } from '@/components/klarhimmel/ui/icon-button';
export default function Example() {
return (
<Theme theme="brand">
<IconButton label="Notifications" shortcut="N">
<Bell aria-hidden className="size-5" />
</IconButton>
</Theme>
);
}Install
Import
import { IconButton } from '@/components/klarhimmel/ui/icon-button';- Lands in
- components/klarhimmel/ui/icon-button.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
Toolbar
Bold is on, italic is off.
import { useState } from 'react'; import { Bold, Italic, Link2 } from 'lucide-react'; import { IconButton } from '@/components/klarhimmel/ui/icon-button'; import { TooltipProvider } from '@/components/klarhimmel/ui/tooltip'; export default function IconButtonToolbar() { const [on, setOn] = useState({ bold: true, italic: false }); return ( <div className="flex min-h-24 flex-col items-start gap-3"> <TooltipProvider> <div role="toolbar" aria-label="Text style" className="flex gap-1 rounded-kh-lg border border-kh-line bg-kh-surface p-1"> <IconButton size="sm" label="Bold" shortcut="B" active={on.bold} tooltipSide="bottom" onClick={() => setOn((s) => ({ ...s, bold: !s.bold }))}> <Bold aria-hidden className="size-4" /> </IconButton> <IconButton size="sm" label="Italic" shortcut="I" active={on.italic} tooltipSide="bottom" onClick={() => setOn((s) => ({ ...s, italic: !s.italic }))}> <Italic aria-hidden className="size-4" /> </IconButton> <IconButton size="sm" label="Add link" tooltipSide="bottom"><Link2 aria-hidden className="size-4" /></IconButton> </div> </TooltipProvider> <p aria-live="polite" className="text-kh-small text-kh-muted">Bold is {on.bold ? 'on' : 'off'}, italic is {on.italic ? 'on' : 'off'}.</p> </div> ); }Toggle
On: bold. A pressed button announces as pressed.
import { useState } from 'react'; import { Bold, Italic, Underline } from 'lucide-react'; import { IconButton } from '@/components/klarhimmel/ui/icon-button'; import { TooltipProvider } from '@/components/klarhimmel/ui/tooltip'; const marks = [ { id: 'bold', label: 'Bold', shortcut: 'B', Icon: Bold }, { id: 'italic', label: 'Italic', shortcut: 'I', Icon: Italic }, { id: 'underline', label: 'Underline', shortcut: 'U', Icon: Underline }, ]; export default function IconButtonToggle() { const [on, setOn] = useState<string[]>(['bold']); return ( <TooltipProvider> <div className="flex flex-col gap-3"> <div role="group" aria-label="Text style" className="flex items-center gap-1"> {marks.map(({ id, label, shortcut, Icon }) => ( <IconButton key={id} label={label} shortcut={shortcut} variant="soft" active={on.includes(id)} onClick={() => setOn((s) => (s.includes(id) ? s.filter((x) => x !== id) : [...s, id]))} > <Icon aria-hidden className="size-5" /> </IconButton> ))} </div> <p aria-live="polite" className="text-kh-small text-kh-muted">On: {on.join(', ') || 'nothing'}. A pressed button announces as pressed.</p> </div> </TooltipProvider> ); }
API reference
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | - | Required. The accessible name, also shown as the tooltip. |
variant | 'ghost' | 'soft' | 'secondary' | 'primary' | 'ghost' | Visual style. |
size | 'sm' | 'md' | 'lg' | 'md' | The small size is 36 px with a 44 px hit area. |
active | boolean | - | Makes it a toggle: sets aria-pressed and the pressed look. |
shortcut | string | - | A shortcut shown in the tooltip. |
noTooltip | boolean | false | Skip the tooltip when the label is already visible nearby. |
tooltipSide / tooltipDelay | 'top' | 'right' | 'bottom' | 'left' / number | 'top' | Where the tooltip opens and the hover delay in ms. |
...button | ButtonHTMLAttributes | - | Native attributes are passed through; type defaults to "button". |
className | string | - | Extra classes, merged after the component's own. |
Accessibility
| Key | Does |
|---|---|
Enter / Space | Activates the button |
Tab | Moves focus; the tooltip shows with it |
Escape | Hides the tooltip |
The label is required: it is the only name a screen reader hears.
Accessibility