IconButton
Icon-only controls with matching `aria-label` and `title` from one `label` prop.
Wraps: Button, size, icon
Spring campaign
Harbor Studio · 12 tasks
Nothing changed yet.
Hover or long-press a button to read its name. Screen readers announce the same text.
Use it
Examples import from the package. With copied source, import from @/components/ui/icon-button instead.
import { Archive, RefreshCw, Share2 } from "lucide-react";
import { IconButton } from "@zedtech/zedkit-ui";
<IconButton label="Refresh tasks" variant="ghost" onClick={() => {}}><RefreshCw /></IconButton>
<IconButton label="Share project" variant="ghost" onClick={() => {}}><Share2 /></IconButton>
<IconButton label="Archive project" variant="outline" onClick={() => {}}><Archive /></IconButton>Row actions in a list
- Write the launch announcementInès · due Nov 4
- Pick the hero photoMalo · due Nov 6
- Review the pricing page copySam · due Nov 12
No task duplicated.
import { Copy, Pencil, Trash2 } from "lucide-react";
import { IconButton } from "@zedtech/zedkit-ui";
{tasks.map((task) => (
<li key={task.id}>
{task.title}
<IconButton label={`Edit ${task.title}`} variant="ghost"><Pencil /></IconButton>
<IconButton label={`Duplicate ${task.title}`} variant="ghost"><Copy /></IconButton>
<IconButton label={`Delete ${task.title}`} variant="ghost" onClick={() => remove(task.id)}><Trash2 /></IconButton>
</li>
))}Kit not installed yet?
Install
Terminal pnpm add @zedtech/zedkit-ui lucide-react @fontsource-variable/inter @fontsource-variable/space-groteskLoad the styles
Tailwind does not scan node_modules, so the @source line points it at the kit's class names. The path is relative to this file.
src/app/globals.cssReplace the contents@import "tailwindcss"; @import "@zedtech/zedkit-ui/styles.css"; @source "../../node_modules/@zedtech/zedkit-ui/src/**/*.{ts,tsx}"; @source not "../../node_modules/@zedtech/zedkit-ui/src/**/*.test.*";Pick a look
Set data-identity="glass" on <html> so the finished app matches the site. Fresh app: replace src/app/layout.tsx. Existing app: merge, and keep ThemeProvider and other providers.
src/app/layout.tsxReplace the contentsimport "./globals.css"; import "./fonts"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en" data-identity="glass"> <body>{children}</body> </html> ); }Load the fonts
The Glass look uses Inter and Space Grotesk; these imports self-host them.
src/app/fonts.tsCreate the fileimport "@fontsource-variable/inter"; import "@fontsource-variable/space-grotesk";Import and render
src/app/page.tsxReplace the contents"use client"; import { Archive, RefreshCw, Share2 } from "lucide-react"; import { IconButton } from "@zedtech/zedkit-ui"; export default function Page() { return ( <main className="px-page-gutter py-section-sm"> <IconButton label="Refresh tasks" variant="ghost" onClick={() => {}}><RefreshCw /></IconButton> <IconButton label="Share project" variant="ghost" onClick={() => {}}><Share2 /></IconButton> <IconButton label="Archive project" variant="outline" onClick={() => {}}><Archive /></IconButton> </main> ); }
Package version 2.0.1.
Props
Show inherited HTML attributes
Standard attributes from the underlying element. See MDN.
Accessibility
label becomes both aria-label and title, so screen readers and hover/long-press tooltips read the same name. Native button with type="button" (never submits a form by accident); a visible focus ring shows on keyboard focus. Decorative glyphs inside should be icons, not text the reader would announce twice.
Troubleshooting
A TypeScript error about a missing `label` — it is required on purpose, an icon-only control without a name is unusable with a screen reader. Several identical buttons in a list — put the row name in the label (`Delete ${task.title}`) so each is distinct. Button feels too big for a dense toolbar — the size defaults to `icon`; pass `size="sm"` for a compact one (the glyph keeps the kit icon size). The button submits a form — it should not (`type="button"`); pass `type="submit"` explicitly if you really want that.