Buttons
Covers: primary/secondary buttons, CTAs, icon buttons, destructive actions, form actions, touch targets.
Design Rules
- Button shadows follow
shadows.md: never pairshadow-*with solid gray borders; usering-1 ring-black/5orring-1 ring-black/10instead - Primary buttons with a ring: never use reduced ring opacity; use a solid color matching the button background (e.g.
ring-indigo-600on abg-indigo-600button, notring-black/10) - Dangerous actions (e.g. "Delete") use a secondary/muted style by default: only use a primary style when the dangerous action is the page's or dialog's primary action (e.g. a confirm-delete dialog)
- Only one primary button per page: scan the whole page and ensure only one uses a filled/solid primary style; every other must use secondary, soft/muted (solid with opacity), outline, or ghost (text-only); treat dialogs/modals as their own page
- Never make a secondary button higher contrast than the primary: the primary must always be the most visually prominent
- Any button that is not the page's primary submit/save action is an inline form action (change avatar, change photo, upload file, generate password, verify email, add item, resend code, etc.): always use the smaller of the two button sizes and a secondary style; never the same height as the form's primary/submit button
- Choice buttons, presets, segmented controls, and toggle buttons need a persistent selected state. Hover is not selection: the selected state stays unmistakable once the pointer leaves. Use
aria-pressedon a toggle button,role="radio"witharia-checkedinside aradiogroupfor a single-choice preset set, andaria-selectedonly on the roles that accept it (option,tab,row,gridcell,treeitem).aria-selectedon a plain<button>is invalid.
Sizing
- Less horizontal padding:
px-3 py-2notpx-4 py-2,px-4 py-3notpx-5 py-3 - Application UIs (dashboards, settings, admin):
text-smwith compact padding, nevertext-base; total rendered button height (including outer wrapper/ring) must stay within 28-38px; account for thep-pxborder wrapper (adds 2px total) - Maximum 2 button sizes per application UI: pick two distinct heights and use only those; the difference must be at least 6px
- Buttons with a leading/trailing icon: never use symmetric
px-*; usepl-*/pr-*and set the icon side's padding equal to the vertical padding:py-2 pr-3 pl-2(left icon),py-2 pr-2 pl-3(right icon),py-1.5 pr-2.5 pl-1.5(left icon, compact)
Focus Styles
- Solid buttons need a custom focus ring:
focus-visible:outline-*withfocus-visible:outline-offset-2; default tofocus-visible:outline-blue-500if the project has no established focus color
Coding Rules
- Small/icon buttons ship at a 48x48px touch target: make the button
relativeand add<span class="absolute top-1/2 left-1/2 size-[max(100%,3rem)] -translate-1/2 pointer-fine:hidden" aria-hidden="true" />as a direct child. This file owns the 48px build default. It sits deliberately above the 44x44 WCAG 2.5.5 floor thatrules/interaction-target-size.mdaudits to, so a later padding change cannot drop the control under conformance - Interactive labels: apply
select-none(oruser-select: none) on button and control inner text so drag-select does not fight clicks. Do not disable selection on body copy or inputs.