Skip to content
Dethink Components

ComponentsRevealButton

RevealButton

Show an icon that reveals its label on hover or keyboard focus.

On this page

Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.

View registry files

Import the component into your page or component file.

Usage
import { RevealButton } from "@dethink/components";

export function Example() {
  return <RevealButton icon={<SearchIcon />} label="Search" />;
}

Try the examples, then open the code to use them in your app.

Variants

Use the same action hierarchy as Button and IconButton while keeping labels available on demand.

Show sourceexamples/reveal-button/basic.tsx
examples/reveal-button/basic.tsx
"use client";

import { RevealButton } from "@dethink/components";
import { Bell, Bookmark, Search, Settings, Trash2 } from "lucide-react";

export function RevealButtonBasic() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <RevealButton icon={<Search />} label="Search" />
      <RevealButton icon={<Bell />} label="Notifications" variant="soft" />
      <RevealButton icon={<Settings />} label="Settings" variant="outline" />
      <RevealButton icon={<Bookmark />} label="Bookmark" variant="ghost" />
      <RevealButton icon={<Trash2 />} label="Delete" variant="destructive" />
    </div>
  );
}

Sizes

Collapsed dimensions match IconButton sizes; the revealed label expands inline from the icon box.

Show sourceexamples/reveal-button/sizes.tsx
examples/reveal-button/sizes.tsx
"use client";

import { RevealButton, type RevealButtonSize } from "@dethink/components";
import { Search } from "lucide-react";

const sizes: RevealButtonSize[] = ["xs", "sm", "md", "lg", "xl"];

export function RevealButtonSizes() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      {sizes.map((size) => (
        <RevealButton
          key={size}
          icon={<Search />}
          label={`${size.toUpperCase()} search`}
          size={size}
          variant="outline"
        />
      ))}
    </div>
  );
}

States

Loading, disabled, always-visible labels, focus styling, and reduced-motion mode.

Show sourceexamples/reveal-button/states.tsx
examples/reveal-button/states.tsx
"use client";

import { RevealButton } from "@dethink/components";
import { RefreshCw, Save, Search, Settings } from "lucide-react";

export function RevealButtonStates() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <RevealButton icon={<Search />} label="Hover or focus" />
      <RevealButton
        className="ring-ring ring-offset-background ring-2 ring-offset-2"
        icon={<Settings />}
        label="Focused"
        variant="outline"
      />
      <RevealButton
        icon={<Save />}
        label="Always visible"
        labelVisibility="always"
        variant="soft"
      />
      <RevealButton
        disabled
        icon={<RefreshCw />}
        label="Disabled"
        variant="outline"
      />
      <RevealButton
        icon={<RefreshCw />}
        label="Loading"
        loading
        variant="outline"
      />
      <RevealButton icon={<RefreshCw />} label="No motion" motion="none" />
    </div>
  );
}

Examples that combine components for common tasks.

Production navbar

A branded workspace header with persistent navigation and RevealButton actions that stay compact until intent is clear.

Northstar OpsProduction control
Show sourceexamples/reveal-button/recipe-navbar.tsx
examples/reveal-button/recipe-navbar.tsx
"use client";

import { RevealButton } from "@dethink/components";
import {
  Bell,
  Command,
  LayoutDashboard,
  Plus,
  Search,
  Settings,
  ShieldCheck,
} from "lucide-react";

const navigationItems = [
  { href: "#overview", label: "Overview", current: true },
  { href: "#pipelines", label: "Pipelines", current: false },
  { href: "#incidents", label: "Incidents", current: false },
  { href: "#reports", label: "Reports", current: false },
];

export function RevealButtonRecipeNavbar() {
  return (
    <header className="border-border bg-background mx-auto w-full max-w-5xl rounded-md border shadow-sm">
      <div className="flex min-h-16 items-center gap-4 px-4 sm:px-5">
        <a
          href="#overview"
          aria-labelledby="reveal-navbar-brand-title reveal-navbar-brand-subtitle"
          className="focus-visible:ring-ring focus-visible:ring-offset-background flex min-w-0 shrink-0 items-center gap-3 rounded-md outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
        >
          <span
            aria-hidden="true"
            className="bg-primary text-primary-foreground grid size-10 shrink-0 place-items-center rounded-md shadow-sm"
          >
            <ShieldCheck className="size-5" />
          </span>
          <span className="min-w-0 max-sm:hidden">
            <span
              id="reveal-navbar-brand-title"
              className="font-heading text-foreground block truncate text-sm font-semibold"
            >
              Northstar Ops
            </span>
            <span
              id="reveal-navbar-brand-subtitle"
              className="text-muted-foreground block truncate text-xs"
            >
              Production control
            </span>
          </span>
        </a>

        <nav aria-label="Workspace" className="hidden min-w-0 flex-1 md:block">
          <ul className="flex items-center justify-center gap-1">
            {navigationItems.map((item) => (
              <li key={item.href}>
                <a
                  href={item.href}
                  aria-current={item.current ? "page" : undefined}
                  className="text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:ring-ring focus-visible:ring-offset-background aria-[current=page]:bg-muted aria-[current=page]:text-foreground inline-flex h-9 items-center rounded-md px-3 text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
                >
                  {item.label}
                </a>
              </li>
            ))}
          </ul>
        </nav>

        <div
          aria-label="Workspace actions"
          className="ml-auto flex shrink-0 items-center gap-1"
          role="toolbar"
        >
          <RevealButton icon={<Search />} label="Search" variant="ghost" />
          <RevealButton
            icon={<Command />}
            label="Command menu"
            variant="outline"
          />
          <RevealButton icon={<Bell />} label="Notifications" variant="soft" />
          <RevealButton
            className="max-sm:hidden"
            icon={<Settings />}
            label="Settings"
            variant="ghost"
          />
          <RevealButton
            className="max-sm:hidden"
            icon={<LayoutDashboard />}
            label="Dashboards"
            variant="ghost"
          />
          <RevealButton icon={<Plus />} label="New run" variant="solid" />
        </div>
      </div>
    </header>
  );
}

Document toolbar

A compact toolbar where actions stay scannable without permanently taking text-button space.

Customer insight report

Draft updated just now

Show sourceexamples/reveal-button/recipe-toolbar.tsx
examples/reveal-button/recipe-toolbar.tsx
"use client";

import { RevealButton } from "@dethink/components";
import { Archive, Copy, Download, Pencil, Share2 } from "lucide-react";

export function RevealButtonRecipeToolbar() {
  return (
    <div className="border-border bg-muted/30 mx-auto flex w-full max-w-2xl items-center justify-between gap-4 rounded-md border p-3">
      <div className="min-w-0">
        <p className="text-foreground truncate text-sm font-medium">
          Customer insight report
        </p>
        <p className="text-muted-foreground text-sm">Draft updated just now</p>
      </div>
      <div
        aria-label="Report actions"
        className="flex shrink-0 items-center gap-1"
        role="toolbar"
      >
        <RevealButton icon={<Pencil />} label="Edit" variant="ghost" />
        <RevealButton icon={<Copy />} label="Duplicate" variant="ghost" />
        <RevealButton icon={<Share2 />} label="Share" variant="ghost" />
        <RevealButton icon={<Download />} label="Export" variant="outline" />
        <RevealButton
          icon={<Archive />}
          label="Archive"
          variant="destructive"
        />
      </div>
    </div>
  );
}

RevealButton renders a real button element and owns its accessible name through label.

RevealButton props
PropWhat it doesDefault
iconReactNodeDecorative icon shown in the collapsed control and replaced by the spinner while loading.Not set
labelstringRequired text used for both the button's accessible name and the revealed visual label.Not set
variant"solid" | "soft" | "outline" | "ghost" | "destructive"Visual treatment matching Button and IconButton variants."ghost"
size"xs" | "sm" | "md" | "lg" | "xl"Collapsed square control size; the revealed label adds logical inline space."md"
labelVisibility"hover" | "always"Reveals on hover and focus by default, or keeps the label visible for high-clarity contexts."hover"
motion"none" | "subtle" | "standard"Controls reveal and press motion. none removes transform-heavy motion."standard"
loadingbooleanSpinner replaces the icon, aria-busy is set, and activation is blocked.false
โ€ฆnative button propsButtonHTMLAttributesRenders a real button: onClick, disabled, aria-pressed, type, form, and related props.Not set