Button

Trigger actions and navigate. Composes variant (solid/outline/ghost/soft/link) with tone (neutral/primary/success/warning/danger/info).

Overview

The Button component is a core building block of the Byteion design system, providing a robust, accessibility-compliant interface for its specific UI capabilities.

Why this exists

Buttons are the most frequent interactive element in any interface. A well-designed button system communicates hierarchy, state, and intent at a glance. Byteion buttons bundle tactile feedback (micro-animations, shadow transitions), full keyboard/ARIA support, and consistent disabled/loading states so you never ship an ambiguous call-to-action.

Mental model

Think of a button as a semantic contract: <button> for actions, <a> styled as a button for navigation. The variant you pick signals priority — only one primary action should exist per section.

When to use

• Primary actions in forms, modals, and CTAs • Secondary or ghost actions for cancel, back, or optional paths • Icon-only buttons in toolbars, data-table row actions, and floating action buttons • Button groups for segmented controls (Day / Week / Month) • Loading states for async form submissions

When not to use

• Do not use buttons for navigation — use links instead • Do not stack multiple primary buttons in one section; it confuses the user about the main action • Avoid gradient buttons for destructive actions — they look celebratory • Do not replace buttons with clickable divs; you lose keyboard activation and screen-reader semantics

Basic example

The following example demonstrates standard usage, fully compliant with Byteion's token-first approach.

Variants

Each variant communicates a different meaning. Choose based on priority, not aesthetics.

  • solid — solid style variant.
  • outline — outline style variant.
  • ghost — Minimal emphasis — cancel, back, optional paths.
  • soft — soft style variant.
  • link — link style variant.

Sizes

Size should match the importance and context of the component.

  • xs — Compact — icon-only toolbars, dense tables.
  • sm — Small — secondary actions, inline forms.
  • md — Medium — standard for most use cases.
  • lg — Large — hero CTAs, mobile touch targets.
  • xl — xl size.

States

This component reacts seamlessly to the following interaction and data states:

  • default — Resting appearance before interaction.
  • hover — Pointer is over the element — visual feedback via color or shadow.
  • focus-visible — focus-visible state.
  • active — Currently selected or in use.
  • disabled — Non-interactive — grayed out, cursor not-allowed.
  • loading — Action in progress — spinner replaces icon, button is disabled.

Anatomy

• Container — rounded rectangle with shadow; padding is proportional to size • Label — short, action-oriented text (Save, Delete, Continue) • Icon — optional leading or trailing icon; icon-only buttons must have aria-label • Spinner — replaces icon during loading; text remains to preserve button width • Group wrapper — overflow-hidden rounded border for segmented controls

Usage guidelines

  • Use exactly one primary button per section or modal.
  • Label should be an action verb, not a noun (Save > Form).
  • Place destructive actions on the right or below the form, never as the primary CTA.
  • Keep icon buttons within toolbars — do not scatter them across page content.
  • Button groups should only contain mutually exclusive options.

Do and Don't

Do

  • Use primary for the single most important action.
  • Use outline or ghost for secondary and cancel actions.
  • Show a loading spinner and disable the button on submit.
  • Wrap icon-only buttons in a consistent container (toolbar, table actions).

Don't

  • Do not use more than one primary button in a modal footer.
  • Do not use gradient buttons for destructive or routine actions.
  • Do not hide the button label — icons alone are ambiguous.
  • Do not change button height within a form row.

Accessibility

Buttons are natively keyboard-activatable (Enter and Space). Byteion buttons add focus-visible:ring for visible focus outlines. Loading buttons set aria-busy="true" and disabled. Icon-only buttons must have aria-label. Button groups use aria-pressed for the active segment.

Role:button

ARIA Attributes

  • aria-disabled
  • aria-pressed
  • aria-busy
  • aria-label

Keyboard Navigation

  • Enter: Activates the button
  • Space: Activates the button

Customization

Change the primary color globally by updating --primary in variables.css. Adjust border radius by modifying the rounded-lg class or the token --radius-button. Change heights by targeting the size-specific classes (h-7, h-8, etc.).

Compose with React cva()

Use the exportedcva() +cn() utilities to compose your own variant on top of the Byteion tokens.

import { cva, cn, type VariantProps } from '@byteion/react';

const myButton = cva('base classes…', {
 variants: {
  accent: { soft:'border-primary-200', loud:'border-primary-500' },
 },
 defaultVariants: { accent:'soft' },
});

type Props = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof myButton>;

Compose with Vue cva()

Use the exportedcva() +cn() utilities inside standard Vue setup blocks.

<script setup lang="ts">
import { computed, useAttrs } from 'vue';
import { cva, type VariantProps } from '@byteion/vue';

interface Props { accent?:'soft' |'loud' }
const props = withDefaults(defineProps<Props>(), { accent:'soft' });
const attrs = useAttrs();

const myButton = cva('base classes…', {
  variants: { accent: { soft:'border-primary-200', loud:'border-primary-500' } },
});

const cls = computed(() => myButton({ accent: props.accent, class: attrs.class as string | undefined }));
</script>

Runtime events

Byteion keeps framework APIs idiomatic: HTML uses DOM custom events, React uses callback props, and Vue uses emitted events / v-model updates.

HTML / JS events

No HTML runtime events are declared by this adapter.

React callbacks

No React callback events are declared by this adapter.

Vue emits

No Vue emitted events are declared by this adapter.

Remote / AJAX

This component does not implement Byteion JS remote loading. Use normal HTML form submission, framework state, or your application data layer around it.

API Reference

No data attributes required for static buttons. For JS-driven loading/toggling, use data-bi-component="button". Interactive states are handled via Tailwind pseudo-classes (:hover, :focus-visible, :active, :disabled).

HTML Data Attributes

No custom data attributes required.

React Props · ButtonProps

Extracted directly fromreact/src/components/Button.tsx.

export interface ButtonProps
  extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children'>,
    ButtonVariantProps {
  leadingIcon?: ReactNode;
  trailingIcon?: ReactNode;
  children?: ReactNode;
  'aria-label'?: string;
  /** Removes text padding and applies square/circle geometry. */
  iconOnly?: boolean;
  /** Screen-reader fallback used when an icon-only button is loading. */
  loadingLabel?: string;
}

React Variant Schema

Internal variant composition defined viacva().

cva(
  'inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-medium transition-all duration-200 ease-out-soft outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background motion-reduce:transform-none motion-reduce:transition-none disabled:pointer-events-none disabled:translate-y-0 disabled:scale-100 disabled:cursor-not-allowed disabled:opacity-60 disabled:shadow-none',
  {
    variants: {
      variant: {
        solid: 'active:scale-[0.98]',
        outline: 'border bg-transparent active:scale-[0.98]',
        soft: 'shadow-none active:scale-[0.98]',
        ghost: 'bg-transparent shadow-none active:scale-[0.98]',
        link: 'bg-transparent px-0 shadow-none underline-offset-4 hover:underline active:opacity-80',
      },
      tone: {
        neutral: '',
        primary: '',
        secondary: '',
        success: '',
        warning: '',
        danger: '',
        destructive: '',
        info: '',
      },
      size: {
        xs: 'h-7 rounded-md px-2.5 text-xs gap-1.5',
        sm: 'h-8 rounded-md px-3 text-xs',
        default: 'h-10 rounded-lg px-4 text-sm',
        md: 'rounded-lg px-4 py-2 text-sm',
        lg: 'rounded-lg px-5 py-2.5 text-sm gap-2.5',
        xl: 'rounded-lg px-6 py-3 text-base gap-2.5',
      },
      shape: {
        default: '',
        pill: 'rounded-full',
        square: 'p-0',
        circle: 'rounded-full p-0',
      },
      block: {
        true: 'w-full',
        false: '',
      },
      loading: {
        true: 'cursor-progress opacity-90',
        false: '',
      },
    },
    compoundVariants: [
      { size: ['xs', 'sm', 'md', 'default', 'lg', 'xl'], shape: ['square', 'circle'], class: 'aspect-square' },
      { size: 'xs', shape: ['square', 'circle'], class: '!h-7 !w-7' },
      { size: 'sm', shape: ['square', 'circle'], class: '!h-8 !w-8' },
      { size: ['md', 'default'], shape: ['square', 'circle'], class: '!h-10 !w-10' },
      { size: 'lg', shape: ['square', 'circle'], class: '!h-11 !w-11' },
      { size: 'xl', shape: ['square', 'circle'], class: '!h-12 !w-12' },

      { variant: 'solid', tone: 'neutral', class: 'bg-neutral-900 text-white shadow-button hover:-translate-y-px hover:bg-neutral-800 hover:shadow-button-hover active:translate-y-0 active:shadow-button focus-visible:ri
 // … truncated

Vue Props · ButtonProps

Extracted directly fromvue/src/components/Button.vue.

export interface ButtonProps {
  variant?: 'solid' | 'outline' | 'soft' | 'ghost' | 'link';
  tone?: 'neutral' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'destructive' | 'info';
  size?: 'xs' | 'sm' | 'default' | 'md' | 'lg' | 'xl';
  shape?: 'default' | 'pill' | 'square' | 'circle';
  block?: boolean;
  loading?: boolean;
  disabled?: boolean;
  iconOnly?: boolean;
  loadingLabel?: string;
  /** `type` attribute on the underlying `<button>`. Defaults to `'button'`. */
  type?: 'button' | 'submit' | 'reset';
}

Real-world examples

See how this component composes in common application contexts.

In a form

In a modal

In a toolbar

Async / Loading state

Developer tips

• Common mistake: using div with onClick instead of <button> — you lose keyboard and screen-reader support. • Edge case: when a form has both Save and Delete, make Save primary and Delete ghost/danger — never two primaries. • Best practice: keep button labels under 3 words. If the label is long, the action is probably ambiguous.

Common mistakes

Avoid hardcoded neutral colors, placeholder-only labels, duplicated primary actions, and missing keyboard states. Keep this component on Byteion tokens so dashboard, admin, POS, ERP, ecommerce back-office, Laravel, React, and Vue starter-kit screens can be rebranded without rewriting markup.

Production usage tips

Use the canonical HTML as the source of truth, keep React and Vue wrappers aligned with the documented variants, test dark mode and branded themes before release, and wire interactive behavior through data-bi-* attributes instead of styling hooks.

Premium examples

Advanced patterns available in Byteion Pro:

• Gradient CTA buttons for upgrade/paywall screens • Animated check-mark after successful save • Floating action button (FAB) with expand-on-hover for mobile

Unlock Premium Patterns

Get access to copy-paste ready Pro blocks, SaaS templates, and advanced data-tables built with this component.

Explore Pro Components