Button
Primary call-to-action element with three visual variants that map to action hierarchy. Supports prefix and suffix slots for icons. Renders as an anchor when given an href, for navigation actions on landing pages, toolbars, and forms.
<arc-button>Overview
Button is the primary CTA element in ARC UI. Its three variants (primary, secondary, and ghost) map to an action hierarchy: primary is the single most important action, secondary is a visible but lower-emphasis alternative, and ghost is a minimal option for tertiary actions.
The prefix and suffix slots hold icons or other inline elements beside the label, such as a search icon or an arrow.
For links, slot a real <a> as the button's only child. That anchor is ordinary HTML in the initial markup, so the link works with JavaScript disabled and before the element upgrades. On a slow connection your hero CTA is clickable immediately instead of after hydration. Button adopts the anchor on upgrade and styles it as the control, so there is only ever one link in the accessibility tree.
The href attribute is still supported and renders an <a> in the shadow root. Use it inside app shells behind a login, where JavaScript is a given and the extra markup is noise. Prefer the slotted-anchor form anywhere the page is public, indexed, or first-load critical, because when the destination lives on the custom element the anchor cannot exist without JavaScript. When no href and no slotted anchor are present, Button behaves as a standard button element for form submissions and interactive triggers.
Three size presets (sm, md, lg) scale buttons to their context. Use lg for hero sections and high-impact CTAs, md for general UI, and sm for compact toolbars or inline actions. All sizes keep the same padding ratios and touch targets.
Guidelines
When to use
- Use primary for the single most important action on the page
- Pair primary with secondary or ghost to create a clear visual hierarchy
- Use the `prefix` slot for leading icons and `suffix` for trailing arrows or indicators
- Use the lg size for hero sections and above-the-fold CTAs
- Slot a real `<a>` for navigation on public pages so the link survives with JavaScript disabled
- Reserve the `href` attribute for app shells where JavaScript is guaranteed
- Keep button labels short and action-oriented (e.g. "Get Started", "View Docs")
When not to use
- Do not place multiple primary buttons side by side. Use one primary per action group
- Do not use ghost variant for the most important action; it is too subtle for primary CTAs
- Do not render navigation as a plain button; this hurts accessibility and SEO
- Do not put a second link inside a slotted anchor; the adopted anchor is already the control
- Do not use long sentences as button labels; aim for two to three words maximum
- Do not disable buttons without explaining why the action is unavailable
Features
- Three variants (primary, secondary, ghost) for action hierarchy
- Three size presets (sm, md, lg) scaled for context
- Prefix and suffix slots for icons or inline elements alongside the label
- Adopts a slotted <a> so links work without JavaScript and before hydration
- Renders as <a> with href for accessible navigation
- Neon glow hover effect on primary variant
- Focus-visible ring for keyboard accessibility
- Scale-down on active press
- Disabled state that prevents interaction and dims the element
- Uppercase Tomorrow type
Preview
Usage
Layout and styling work without JavaScript via the HTML/CSS versions. Interactive features like events and state management require the Web Component or a framework wrapper.
<script type="module" src="@arclux/arc-ui"></script>
<!-- Slot a real <a>: the link works without JS and before hydration -->
<div style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap;">
<arc-button variant="primary" size="lg"><a href="/docs/getting-started">Get Started</a></arc-button>
<arc-button variant="secondary"><a href="/docs/components">View Docs</a></arc-button>
<arc-button variant="ghost"><a href="/docs/tokens">Learn More</a></arc-button>
</div>
<!-- href still works — best inside app shells where JS is guaranteed -->
<arc-button variant="primary" href="/dashboard">Dashboard</arc-button>
<!-- With prefix icon (icons go inside the anchor in the slotted form) -->
<arc-button variant="secondary">
<svg slot="prefix" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path d="M5 4a2 2 0 012-2h6a2 2 0 012 2v14l-5-2.5L5 18V4z"/></svg>
Bookmark
</arc-button>import { Button } from '@arclux/arc-ui-react';
export function HeroActions() {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
<Button variant="primary" size="lg"><a href="/docs/getting-started">Get Started</a></Button>
<Button variant="secondary"><a href="/docs/components">View Docs</a></Button>
<Button variant="ghost"><a href="/docs/tokens">Learn More</a></Button>
</div>
);
}
{/* With prefix icon */}
<Button variant="secondary">
<svg slot="prefix" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path d="M5 4a2 2 0 012-2h6a2 2 0 012 2v14l-5-2.5L5 18V4z"/></svg>
Bookmark
</Button><script setup>
import { Button } from '@arclux/arc-ui-vue';
</script>
<template>
<div style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap;">
<Button variant="primary" size="lg"><a href="/docs/getting-started">Get Started</a></Button>
<Button variant="secondary"><a href="/docs/components">View Docs</a></Button>
<Button variant="ghost"><a href="/docs/tokens">Learn More</a></Button>
</div>
</template><script>
import { Button } from '@arclux/arc-ui-svelte';
</script>
<div style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap;">
<Button variant="primary" size="lg"><a href="/docs/getting-started">Get Started</a></Button>
<Button variant="secondary"><a href="/docs/components">View Docs</a></Button>
<Button variant="ghost"><a href="/docs/tokens">Learn More</a></Button>
</div>import { Component } from '@angular/core';
import { Button } from '@arclux/arc-ui-angular';
@Component({
imports: [Button],
template: `
<div style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap;">
<arc-button variant="primary" size="lg"><a href="/docs/getting-started">Get Started</a></arc-button>
<arc-button variant="secondary"><a href="/docs/components">View Docs</a></arc-button>
<arc-button variant="ghost"><a href="/docs/tokens">Learn More</a></arc-button>
</div>
`,
})
export class HeroActionsComponent {}import { Button } from '@arclux/arc-ui-solid';
export function HeroActions() {
return (
<div style={{ display: 'flex', 'align-items': 'center', gap: '12px', 'flex-wrap': 'wrap' }}>
<Button variant="primary" size="lg"><a href="/docs/getting-started">Get Started</a></Button>
<Button variant="secondary"><a href="/docs/components">View Docs</a></Button>
<Button variant="ghost"><a href="/docs/tokens">Learn More</a></Button>
</div>
);
}import { Button } from '@arclux/arc-ui-preact';
export function HeroActions() {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
<Button variant="primary" size="lg"><a href="/docs/getting-started">Get Started</a></Button>
<Button variant="secondary"><a href="/docs/components">View Docs</a></Button>
<Button variant="ghost"><a href="/docs/tokens">Learn More</a></Button>
</div>
);
}<!-- Auto-generated by @arclux/prism — do not edit manually -->
<!-- arc-button — requires button.css + base.css (or arc-ui.css) -->
<span class="arc-button">
<a class="btn" href="#">Button</a>
</span><!-- Auto-generated by @arclux/prism — do not edit manually -->
<!-- arc-button — self-contained, no external CSS needed -->
<style>
.arc-button:not([data-variant]) .btn:hover,
.arc-button[data-variant="primary"] .btn:hover { box-shadow: 0 0 8px rgba(77,126,247,0.9), 0 0 20px rgba(77,126,247,0.5), 0 0 44px rgba(77,126,247,0.25), 0 0 80px rgba(77,126,247,0.1); }
.arc-button:not([data-variant]) .btn:active,
.arc-button[data-variant="primary"] .btn:active { transform: scale(0.97); box-shadow: 0 0 8px rgba(77, 126, 247,0.5); }
.arc-button[data-variant="secondary"] .btn:hover { border-color: rgb(77, 126, 247);
color: rgb(77, 126, 247);
box-shadow: 0 0 20px rgba(77, 126, 247, 0.15); }
.arc-button[data-variant="secondary"] .btn:active { transform: scale(0.97);
background: rgba(77, 126, 247,0.05); }
.arc-button[data-variant="ghost"] .btn:hover { color: rgb(232, 232, 236);
background: rgba(255, 255, 255,0.03); }
.arc-button[data-variant="ghost"] .btn:active { transform: scale(0.97);
background: rgba(255, 255, 255,0.06); }
.arc-button .btn:focus-visible { outline: none; box-shadow: 0 0 0 2px rgb(3, 3, 7), 0 0 0 4px rgb(77, 126, 247), 0 0 16px rgba(77,126,247,0.3); }
</style>
<span class="arc-button" style="display: inline-flex">
<a class="btn" style="display: inline-flex; align-items: center; justify-content: center; gap: 8px; font-family: 'Tomorrow', system-ui, sans-serif; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; border: 1px solid transparent; border-radius: 10px; cursor: pointer; text-decoration: none; white-space: nowrap; box-sizing: border-box" href="#">Button</a>
</span>API
hrefstring''- When provided, the button renders as an <a> element instead of a <button>, making it a navigational link. This is the recommended approach for any action that takes the user to a new page or section.
variant'primary' | 'secondary' | 'ghost''primary'- Controls the visual weight and emphasis. Primary is a filled button with a neon glow hover suited for the top-level CTA. Secondary uses a bordered outline for supporting actions. Ghost renders with no border or background, ideal for low-priority or tertiary actions.
size'sm' | 'md' | 'lg''md'- Sets the button size. Large (lg) is intended for hero sections and high-impact areas. Medium (md) is the default for general UI. Small (sm) fits compact toolbars, table rows, and inline contexts.
disabledbooleanfalse- When true, dims the button and prevents all pointer and keyboard interaction. Applies reduced opacity and removes hover/focus effects. Consider pairing with a tooltip that explains why the action is unavailable.
loadingbooleanfalse- Shows a spinner and disables the button. Use for async operations like form submission or API calls.
type'button' | 'submit' | 'reset''button'- Sets the HTML button type attribute. Use
submitinside forms to trigger native form submission, orresetto clear form fields. Only applies when nohrefis set (link buttons ignore this).
See Also
- Icon ButtonCompact button that renders an icon with optional text label, supporting ghost, secondary, and primary variants.
- ChipA toggleable pill-shaped element for filters, tags, or multi-select options, with a selected state highlighted in accent-primary.
- LinkStyled anchor with nav, muted, and default variants.