ui · Primitive
Button
@devalok/shilp-sutra/ui/buttonView in Storybook Preview
Variants
variant
color
size
shape
with icons
loading + disabled
Reference
Props
variant: "solid" | "soft" | "outline" | "ghost" | "link"
color: "accent" | "error" | "success" | "warning" | "neutral"
size: "xs" | "sm" | "md" | "lg" | "compact-xs" | "compact-sm" | "compact-md" | "icon" | "icon-xs" | "icon-sm" | "icon-md" | "icon-lg"
weight: "semibold" | "normal"
shape: "default" | "pill"
startIcon: ReactElement (use <Icon icon={...} />) | null
endIcon: ReactElement (use <Icon icon={...} />) | null
loading: boolean (disables button, shows spinner)
loadingPosition: "start" | "end" | "center" (default: "start")
fullWidth: boolean
asChild: boolean
processing: boolean | 'ambient' | 'working' | 'urgent' (marching ants SVG border)
processingColor: 'accent' | 'error' | 'success' | 'warning' | 'neutral' (override animation color)
processingDisabled: boolean (disable button during processing, default: true)
onClickAsync: (e: MouseEvent) => Promise<void> (auto loading->success/error->idle, auto-activates processing)
asyncFeedbackDuration: number (ms, default 1500)
Defaults
variant="solid", color="accent", weight="semibold", size="md", shape="default"
Example
<Button variant="solid" color="error" startIcon={<Icon icon={IconTrash} />} loading={isDeleting}>
Delete project
</Button>
<Button variant="soft" color="success" startIcon={<Icon icon={IconCheck} />}>Approved</Button>
<Button variant="soft" color="warning" size="compact-sm" shape="pill">Overdue</Button>
Composability
- ButtonGroup context consumption: When nested inside
<ButtonGroup>, Button auto-inherits variant/color/size/weight/shape/disabled. Explicit props on the individual Button override. The context also drives position-aware border-radius (first/middle/last within an attached group). - IconProvider cascade: Icons in
startIcon/endIconauto-size via IconProvider per the button size (xs→sm, sm→sm, md→md, lg→md, icon-xs→xs, icon-lg→lg). Don't pass explicitsizeto<Icon>inside Button. - asChild for router links:
<Button asChild><Link href="/foo">...</Link></Button>transfers Button's styling to the Link while preserving navigation semantics. Required for Next.js<Link>/ react-router<Link>. - onClickAsync state machine: Overrides onClick. Auto-cycles
idle → loading (aria-busy, spinner) → success (checkmark) → idleon resolve;loading → error (X mark) → idleon reject. Duration controlled byasyncFeedbackDuration(1500ms default). Auto-activatesprocessing='working'during loading — marching-ants border keeps users visually aware. - Processing vs loading:
loadingis a short async state (shows spinner, blocks clicks).processingis a longer-running state (marching ants border, may or may not block clicks based onprocessingDisabled). Use onClickAsync for simple request cases; use processing explicitly for long-running background operations. - DevalokGrain children: Grain elements are auto-extracted and rendered as direct button children for absolute positioning — lets you layer grain texture on solid-variant buttons without breaking layout.
- Prefer
variant="soft"overvariant="outline"for secondary actions (see Gotchas for details). This is a design-system-wide convention.
Gotchas
- Prefer
variant="soft"overvariant="outline"for secondary actions. Soft (tinted step-3 bg, step-11 text) is the Devalok-recommended default — it feels warmer and brand-consistent. Useoutlineonly when soft's tint would disappear (on colored/surface-raised bg), in toolbar/icon-dense contexts, or when you need outline's stronger hierarchy next to a primary action. - DO NOT use variant="destructive" — use variant="solid" color="error"
- DO NOT use variant="secondary" — use variant="soft" (preferred) or variant="ghost"
- DO NOT use size="default" — use size="md"
- DO NOT use color="danger" or color="default" — use color="error" or color="accent"
- startIcon/endIcon now expect
<Icon icon={...} />wrapper, not bare icon components - Inherits variant/color/size/weight/shape from ButtonGroup context if present
- onClickAsync overrides onClick and loading when active; also auto-activates processing='working' during loading phase
- processing forces soft variant so marching ants pop against the background
- processingDisabled=true (default) makes button aria-disabled and pointer-events-none during processing
- Grain children (DevalokGrain) are auto-separated and rendered as direct button children for absolute positioning
Changes
v0.33.0
- Added
disabledinherited from ButtonGroup context - Added Position-aware border-radius when inside an attached ButtonGroup (compound component pattern)
v0.29.0
- Added
softvariant — tinted background, colored text (new middle ground between solid and ghost) - Added 5 color options:
accent(default),error,success,warning,neutral(replaces olddefault/error-only axis) - Added
shapeprop:"default"|"pill"(rounded-full with extra horizontal padding) - Added compact sizes:
compact-xs,compact-sm,compact-md(height-less inline buttons) - Added
xssize andicon-xssize - Added
weightprop:"semibold"(default) |"normal"for lighter labels - Changed
startIcon/endIconnow accept<Icon icon={...} />wrapper (auto-sized via IconProvider context per button size) - Changed Default color is now
"accent"(was"default") - Changed Solid hover adds tinted shadows per color (e.g.,
hover:shadow-brand,hover:shadow-error) - Changed Icon slots get negative-margin inset to tighten padding against button edges
- Added DevalokGrain support — grain children are auto-separated and rendered for texture overlays
- Added
processingprop — marching ants SVG border while content stays visible ("ambient"(3s) |"working"(2s) |"urgent"(1s) | boolean). Forces soft variant so ants pop. - Added
processingColor— override processing animation color independently of button color - Added
processingDisabled— disable button during processing (default: true). Set false for cancel-by-click patterns. - Added Auto-processing during
onClickAsync— loading phase auto-activatesprocessing='working'when no explicitprocessingprop is set - Added Always-on layout animation — smooth width/height transitions via Framer Motion FLIP
v0.22.0
- Changed Active/pressed scale from
0.97to0.95for snappier press feedback. - Fixed Ghost/outline hover not fading —
transition-transformin base overrodetransition-colorsfrom variant. Combined into singletransition-[color,background-color,border-color,box-shadow,transform]. - Added
disabled:cursor-not-allowedto button base (was missing).
v0.18.0
- Added
onClickAsyncprop — promise-driven loading -> success/error state machine - Added
asyncFeedbackDurationprop (default 1500ms) - Changed whileTap scale animation added via Framer Motion
- Fixed Async feedback colors —
bg-success text-text-on-colorchanged tobg-success-9 text-accent-fg - Fixed
onClickAsyncaddedisMountedRefguard to prevent set-state-after-unmount
v0.4.2
- Fixed
Omit<HTMLAttributes, 'color'>resolves TS2320 conflict with CVA color prop - Fixed
classNamewas passed insidebuttonVariants()(silently dropped by CVA) — now separatecn()argument
v0.3.0
- Changed (BREAKING)
variant="primary"renamed tovariant="solid",variant="secondary"renamed tovariant="outline",variant="error"moved tocolor="error" - Fixed All dismiss/close buttons now meet WCAG 2.5.8 minimum 24px touch target
v0.1.0
- Added Initial release