@ai-matrx/tap-target
The AI Matrx tap-target button system: every button occupies one fixed 38px
footprint holding a 32px round pill and 16px glyph, so neighbouring buttons
sit exactly 6px apart with no wrapper (wide shapes — capsules, labelled pills —
are 28px tall; a 24px pill in the slim group geometry; a
touch area toward 44px on coarse pointers through an invisible hit slop), plus
TapTargetSearchGroup — the macOS toolbar search, with press feedback, mobile tap hygiene (no 300ms delay, no iOS gray
flash), and correct ref forwarding for Radix asChild triggers.
- Primitives —
TapTargetButton(glass),TapTargetButtonTransparent,TapTargetButtonSolid,TapTargetButtonDestructive,TapTargetButtonForGroup+TapTargetButtonGroup, and theTapTargetLabeledcaption wrapper. - ~60 pre-composed buttons —
BugTapButton,PlusTapButton,TrashTapButton, … one import, one tag, zero configuration, all glyphs inlined. Opt-in via the./buttonssubpath. - Every button can be a button, label, or link (
as="label",href), carries automatic tooltip wiring fromariaLabel, and supports an inline caption (label) that widens the pill.
npm install @ai-matrx/tap-target
Peer: react >= 18. The canonical primitive, geometry and tooltip behavior
come from the regular @ai-matrx/design-system dependency.
Setup — two one-time steps
1. Import the stylesheet once at your app root. The CSS is part of the
product — the geometry and press feedback live in it, and nothing renders
correctly without it. Declare its lock layer FIRST in your root CSS
(@layer matrx-tap-lock; before any other rule) so no class can bend it, and
turn on the misuse guard in development builds (enableTapTargetGuard()):
// app/layout.tsx (or your root entry)
import "@ai-matrx/tap-target/styles.css";
That one line is enough: since 0.2.1 this sheet @imports
@ai-matrx/design-system/styles.css, which it REQUIRES — the loading button's
spinner wears design-system's matrx-spin, whose keyframe is defined there
(never animate-spin, a host utility this package cannot ship). If your app
already imports the design-system sheet, nothing is duplicated.
2. Mount a tooltip provider near the root (skip if your app already mounts
@radix-ui/react-tooltip's provider):
import { TooltipProvider } from "@ai-matrx/tap-target";
<TooltipProvider delayDuration={300}>{children}</TooltipProvider>
Link component registration
href on any tap button renders a link. External hrefs (http(s):, mailto:,
tel:) always render <a target="_blank" rel="noopener noreferrer">. Internal
hrefs render through your router's link component — register it once at
startup:
import Link from "next/link";
import { setTapTargetLinkComponent } from "@ai-matrx/tap-target";
setTapTargetLinkComponent(Link); // e.g. in a client boot component
Without a registration, internal hrefs fall back to a plain <a> (still a
working link — no prefetch, no client-side routing). A per-instance override
is also available: <TapTargetButton href="/tasks" linkComponent={Link} />.
Usage
import { TapTargetButton, TapTargetButtonGroup, TapTargetButtonForGroup } from "@ai-matrx/tap-target";
import { PlusTapButton, TrashTapButton } from "@ai-matrx/tap-target/buttons";
// Pre-composed (preferred): one tag, tooltip auto-derives from ariaLabel
<PlusTapButton onClick={add} ariaLabel="Add a task" />
// Variants: glass (default) | transparent | solid | group
<TrashTapButton variant="solid" bgColor="bg-destructive" iconColor="text-destructive-foreground" />
// Links
<PlusTapButton href="/tasks/new" ariaLabel="New task" />
// Inline caption (widens the pill, tooltip off — the label self-describes)
<PlusTapButton label="Add" />
// Grouped slim buttons in one shared glass shell
<TapTargetButtonGroup>
<TapTargetButtonForGroup ariaLabel="Undo" icon={<UndoIcon />} />
<TapTargetButtonForGroup ariaLabel="Redo" icon={<RedoIcon />} />
</TapTargetButtonGroup>
// Custom glyph via the primitive
<TapTargetButton ariaLabel="Custom" icon={<MyIcon className="h-4 w-4" />} />
Refs forward to the underlying <button>, so every button works as a Radix
asChild trigger (DropdownMenu, Popover, Tooltip, …):
<DropdownMenuTrigger asChild>
<MoreHorizontalTapButton />
</DropdownMenuTrigger>
Subpaths
| Subpath | What it is |
|---|---|
. | Primitives, TapTargetLabeled, link registration, tooltip re-exports. |
./buttons | The ~60 pre-composed buttons (heavy — every glyph inlined; opt-in). |
./styles.css | The REQUIRED geometry/feedback stylesheet — import once at app root. |
./package.json | The manifest. |
Styling notes
- Geometry and press feedback come from
styles.css(.matrx-tap-*). To retune every tap button, that file is the single source of truth — never per-button size overrides. - Variant decoration uses Tailwind-style utility classes on the pill
(
bg-primary,hover:bg-muted,text-foreground, and the Matrxmatrx-glass-thin-border/matrx-glass-interactiveglass classes). In a Tailwind app these resolve against your theme tokens; make sure your Tailwind build scans this package (Tailwind v4:@source "../node_modules/@ai-matrx/tap-target";) and provide the twomatrx-glass-*classes (or accept an undecorated glass pill). - Never wrap a tap button in extra
p-*/m-*/gap-*— the box already reserves the spacing. With the guard on, such a button renders as a red box. - Release timing probe (2026-10-09): this line exists to measure push-to-npm for a one-line change.