Components · General
Button
Trigger an action or navigation — the primary interactive control.
Examples
Variant
Size
State
Loading
Tones · emphasis (md)
Primary-alt · text tones (md)
Sizes (primary) — sm 28 · md 36 · lg 40 (default)
Icon+label · loading · disabled · block (full-width)
Icon only (square) · primary · secondary · tertiary
<!-- Paste-and-run: save as .html and open in a browser. No build step, no bundler.
<aha-button> is the SAME shared custom element React and Vue consume — here in its
native form. Theming comes only from the --aha-* tokens in tokens.css. -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/tokens.css">
<script type="module">
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-button.js'; // registers <aha-button>
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/icons.js'; // registers <aha-icon> for the icon slot
</script>
<div style="display:flex; gap:8px">
<aha-button variant="secondary">Cancel</aha-button>
<aha-button variant="primary" onclick="alert('Saved')">
<span slot="icon"><aha-icon name="system-plus" size="16" decorative></aha-icon></span>
Save changes
</aha-button>
</div>
<!-- Size + variant are attributes; never inline height/padding/radius/fontSize. -->
<aha-button variant="primary" size="lg" block>Get started</aha-button>
<aha-button icon-only variant="tertiary" aria-label="Close"><aha-icon name="system-x" size="16" decorative></aha-icon></aha-button>
<!-- Low-emphasis label buttons: text / text-link are borderless. primary-alt is a second primary slot. -->
<aha-button variant="text">Skip</aha-button>
<aha-button variant="text-link">Learn more</aha-button>
import '@ahaslides-product/design/aha-button'; // registers <aha-button>
import '@ahaslides-product/design/icons'; // registers <aha-icon> for the icon slot
import { useRef } from 'react';
// React 19 can use <aha-button> directly; React 18 uses this thin wrapper so onClick + slots work.
function AhaButton({ variant = 'secondary', size = 'md', disabled, loading, iconOnly, icon, children, ...rest }) {
const ref = useRef();
return (
<aha-button
ref={ref}
variant={variant}
size={size}
{...(disabled ? { disabled: true } : {})}
{...(loading ? { loading: true } : {})}
{...(iconOnly ? { 'icon-only': true } : {})}
{...rest}
>
{icon ? <span slot="icon">{icon}</span> : null}
{children}
</aha-button>
);
}
export function SaveBar({ onSave, saving }) {
return (
<div style={{ display: 'flex', gap: 8 }}>
<AhaButton variant="secondary">Cancel</AhaButton>
<AhaButton variant="primary" loading={saving} onClick={onSave}
icon={<aha-icon name="system-plus" size={16} decorative />}>
Save changes
</AhaButton>
</div>
);
}
<!-- Vue 3 consumes the same <aha-button> custom element natively.
Tell the compiler it's a custom element (vite: compilerOptions.isCustomElement = t => t.startsWith('aha-')). -->
<script setup>
import '@ahaslides-product/design/aha-button'; // registers <aha-button>
import '@ahaslides-product/design/icons'; // registers <aha-icon> for the icon slot
defineProps({ saving: Boolean });
const emit = defineEmits(['save']);
</script>
<template>
<div style="display:flex; gap:8px">
<aha-button variant="secondary">Cancel</aha-button>
<aha-button variant="primary" :loading="saving" @click="emit('save')">
<span slot="icon">
<aha-icon name="system-plus" :size="16" decorative />
</span>
Save changes
</aha-button>
</div>
</template>
API
| Prop | Type | Default | Notes |
|---|---|---|---|
variant | primary | secondary | tertiary | link | danger | success | positive | primary-alt | text | text-link | secondary | tone × emphasis; one primary per view. success is a synonym of positive; primary-alt of primary; text / text-link are borderless label buttons |
size | sm | md | lg | lg | 28 / 36 / 40px height — lg (40) is the default |
disabled | boolean | false | non-interactive; grey fill |
loading | boolean | false | shows a spinner, blocks clicks |
block | boolean | false | full-width |
iconOnly | boolean (attr icon-only) | false | square, label-less; requires aria-label |
icon | slot | — | leading glyph via <span slot="icon">; for icon-only, the glyph is the default child |
Install
# .npmrc — once: point the @ahaslides-product scope at GitHub Packages
@ahaslides-product:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} # a GitHub token with read:packages
npm i @ahaslides-product/design
import '@ahaslides-product/design/tokens.css'; // once, at the app root
import '@ahaslides-product/design/aha-button'; // registers <aha-button>
Agent feed for this component (absolute, fetchable anywhere): button.agent.json · button.md · button.llms.txt
When to use
When to use
- primary — the single most important action on a view (one per screen)
- secondary — the default for most actions — neutral, white with a border
- tertiary / link — low-emphasis or inline navigation, no container weight
- danger — a destructive action (delete, remove) — pair with a confirm
- positive — a confirming/success action where a positive signal helps
- icon-only — a compact action where the glyph is self-explanatory (close, more, add) — always give an aria-label
Size: LARGE (lg) is the default for most actions; step down to MEDIUM (md) or SMALL (sm) in denser/secondary regions to lower an action's information-hierarchy, not by whim. Size only via the size attribute — never inline height/padding/radius/fontSize. Primary fill is #6A1EBB; hover #8644D4 per DS V3.
Surfaces
editor dashboard settings audience
Spec
Sizes sm 28 (r4) · md 36 (r8) · lg 40 (r8, default) — heights; fontSize 14 (sm/md) / 16 (lg) · Primary bg #6A1EBB, label #FDFDFD, hover #8644D4, active #5715A0 · Secondary white bg, #1A1A1A label, 1px #E3E3E3 border; hover brand-tint #F9F5FF + #A96FF0 border · Tertiary / Link transparent; tertiary hover #F9F5FF, active #F0E4FF; link #6A1EBB weight 400 · Danger bg #F5222D, white label, hover #FF4D4F, active #CF1322 · Positive / Success bg #4EF1C5, dark label #1A1A1A, hover #93F5DA, active #20E8B5 (success = alias of positive) · Primary-alt same fill as primary #6A1EBB (a second primary slot for a two-primary layout) · Text / Text-link white bg, no border, weight 400; text = secondary-grey label, hover grey fill; text-link = #6A1EBB label, hover #A96FF0 · Disabled bg #E3E3E3, label #B5B5B5 · Label / icon Plus Jakarta Sans SemiBold 600, 14 (sm/md) / 16 (lg/xl); icon 16px, inline-flex, 8px gap · Focus soft box-shadow ring, spread 2 — #D3B4FF@30% (default) · danger #FF2850@20% · positive #20E8B5@30% · Elevation subtle drop-shadow (primary rgba0,0,0,.04 · secondary .016) · Icon-only square (width = height per size: 28/36/40/52), glyph centred, no label; needs aria-label