Glin UI
Primitive / Atom

Button

Action control with default, glass, liquid, matte, glow, outline, and ghost variants.

Edit on GitHub

Release notes for v2.4

Unsaved changes

Edited a few minutes ago by Maya Chen. Changes apply to the public changelog.

Installation

Install from
pnpm dlx @glinui/cli@latest add button

Prefer the package?

Usage

Variants

light scope

variantneutralaccentsuccesswarningdangerinfo
glinr
solid
plain
soft
outline
ghost
gradient
glass

dark scope

variantneutralaccentsuccesswarningdangerinfo
glinr
solid
plain
soft
outline
ghost
gradient
glass
import { Button, SURFACE_TONES, SURFACE_VARIANTS } from "@glinui/ui"
export function Demo() {
return (
<div className="grid gap-2">
{SURFACE_VARIANTS.map((variant) => (
<div key={variant} className="flex flex-wrap gap-2">
{SURFACE_TONES.map((tone) => (
<Button key={tone} variant={variant} tone={tone}>
{variant} {tone}
</Button>
))}
</div>
))}
</div>
)
}

Glass (opt-in)

import { Button } from "@glinui/ui"
// Glass needs a colorful or photographic backdrop to read.
export function Demo() {
return <Button variant="glass">Glass</Button>
}

Sizes, icons and states

Five sizes, leading and trailing icons, loading and disabled.

import { ArrowRight, Plus } from "@phosphor-icons/react"
import { Button } from "@glinui/ui"
export function ButtonStatesDemo() {
return (
<div className="grid w-full max-w-2xl gap-5">
<div className="flex flex-wrap items-end gap-3">
<Button size="xs">Extra small</Button>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
<Button size="icon" variant="outline" aria-label="Add a member">
<Plus weight="bold" />
</Button>
</div>
<div className="flex flex-wrap items-center gap-3">
<Button variant="solid" tone="accent" leadingIcon={<Plus weight="bold" />}>
New project
</Button>
<Button variant="outline" trailingIcon={<ArrowRight weight="bold" />} iconNudge>
Continue
</Button>
<Button variant="solid" tone="accent" loading>
Publishing
</Button>
<Button variant="outline" loading>
Syncing
</Button>
<Button disabled>Unavailable</Button>
<Button variant="solid" tone="danger" disabled>
Delete
</Button>
</div>
</div>
)
}

In a layout

A page header with a quiet filter, an outline export and one primary action.

Invoices

48 invoices this quarter, 3 overdue.

import { DownloadSimple, Funnel, Plus } from "@phosphor-icons/react"
import { Button, Card, CardContent } from "@glinui/ui"
export function ButtonPageHeader() {
return (
<Card className="w-full max-w-3xl">
<CardContent className="flex flex-wrap items-center justify-between gap-4">
<div className="min-w-0">
<h3 className="text-lg font-semibold text-foreground">Invoices</h3>
<p className="text-sm text-[var(--color-muted)]">48 invoices this quarter, 3 overdue.</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<Button variant="ghost" leadingIcon={<Funnel weight="bold" />}>
Filter
</Button>
<Button variant="outline" leadingIcon={<DownloadSimple weight="bold" />}>
Export CSV
</Button>
<Button variant="solid" tone="accent" leadingIcon={<Plus weight="bold" />}>
New invoice
</Button>
</div>
</CardContent>
</Card>
)
}

Size

Variants

Toggle backgrounds (top-right) to see how each variant adapts to different surfaces.

import { Button } from "@glinui/ui"

export function ButtonVariantDemo() {
  return (
    <div className="flex flex-wrap items-center gap-2">
      <Button>Default</Button>
      <Button variant="glass">Glass</Button>
      <Button variant="frosted">Frosted</Button>
      <Button variant="liquid">Liquid</Button>
      <Button variant="matte">Matte</Button>
      <Button variant="glow">Glow</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="ghost">Ghost</Button>
    </div>
  )
}

With Icon

import { ArrowRight } from "@phosphor-icons/react"
import { Button } from "@glinui/ui"

export function ButtonWithIconDemo() {
  return (
    <Button variant="liquid" className="gap-2">
      Continue
      <ArrowRight className="size-4" />
    </Button>
  )
}

Button Group

import { Button } from "@glinui/ui"

export function ButtonGroupDemo() {
  return (
    <div className="inline-flex items-center gap-1 rounded-full border border-border/60 bg-background/45 p-1">
      <Button size="sm" className="rounded-full">Day</Button>
      <Button size="sm" variant="ghost" className="rounded-full">Week</Button>
      <Button size="sm" variant="ghost" className="rounded-full">Month</Button>
    </div>
  )
}

RTL

Use `dir="rtl"` on a layout wrapper to mirror button flow for right-to-left locales.

import { Button } from "@glinui/ui"

export function ButtonRtlDemo() {
  return (
    <div dir="rtl" className="flex justify-center">
      <div className="flex items-center gap-2">
        <Button variant="glass">حفظ</Button>
        <Button variant="outline">إلغاء</Button>
      </div>
    </div>
  )
}

Internationalized Labels

Keep labels outside the component and inject locale strings. Code blocks in docs stay `ltr` for readability. ```tsx import { Button } from "@glinui/ui" const copy = { en: { save: "Save", cancel: "Cancel" }, ar: { save: "حفظ", cancel: "إلغاء" } } export function SaveActions({ locale = "en" }: { locale?: "en" | "ar" }) { const t = copy[locale] return ( <div dir={locale === "ar" ? "rtl" : "ltr"} className="flex gap-2"> <Button>{t.save}</Button> <Button variant="ghost">{t.cancel}</Button> </div> ) } ```

Key and key-white

Tactile gradient keys. Pressed state moves 1px and scales to 0.98. Use iconNudge for an arrow that slides on hover.

import { Button } from "@glinui/ui"
import { ArrowRight } from "@phosphor-icons/react"

export function Demo() {
  return (
    <div className="flex flex-wrap gap-3">
      <Button variant="key" iconNudge>Get started <ArrowRight /></Button>
      <Button variant="key-white" size="sm">Docs</Button>
      <Button variant="raised">Raised ring</Button>
    </div>
  )
}

Accessibility

  • Native `button` semantics by default.
  • Visible 2px accent focus ring with offset on every variant (plain uses a neutral ring).
  • `disabled` and `loading` block interaction, lower emphasis and set `aria-disabled` / `aria-busy`.
  • Icon-only buttons need an `aria-label`.
  • Supports `asChild` for semantic link rendering.

Keyboard Interactions

KeyAction
EnterActivate the button.
SpaceActivate the button.

ARIA Attributes

  • `role="button"` native
  • `aria-disabled` when disabled or loading
  • `aria-busy` while loading

Reduced Motion

Transitions use tokenized timing and disable transform-heavy motion with `prefers-reduced-motion`.

Affected properties

transformopacitybox-shadowbackground-color

API Reference

PropTypeRequiredDefaultDescription
variantButtonVariantNoambient (glinr)Visual treatment. Omit for the ambient style default (glinr, plain under style=minimal, glass under style=glass). Legacy names default, primary, secondary, destructive, frosted and raised still work as aliases.
toneButtonToneNoneutralColour axis for the vocabulary variants.
sizeButtonSizeNomdHeight and horizontal padding scale. `icon` is a square button, give it an `aria-label`.
leadingIconReact.ReactNodeNo-Decorative icon before the label.
trailingIconReact.ReactNodeNo-Decorative icon after the label.
loadingbooleanNofalseShows a spinner, sets aria-busy and blocks interaction.
iconNudgebooleanNofalseNudges icons toward the trailing edge on hover and focus.
asChildbooleanNofalseRenders child element with button styles.

Source

Import directly from the package or browse the source on GitHub. Click any file to view it.

Import

import { Button } from "@glinui/ui"

Source Files

Dependencies

@glinui/ui

Generated API Snapshot

Beta

Auto-extracted from TypeScript source in packages/ui/src/components/button.tsx. This section is in beta and may lag behind hand-curated docs. Regenerate with pnpm --filter @glinui/docs api:generate.

Generated: 2026-10-08T01:41:21.546Z · Full index: /docs/api-metadata

Primary Props Type

ButtonProps

ButtonProps

PropTypeRequiredDefaultDescription
asChildbooleanNofalse-
iconNudgebooleanNo--
leadingIconReact.ReactNodeNo-Icon before the label (decorative).
loadingbooleanNofalseShow a spinner, set `aria-busy` and block interaction.
sizeButtonSizeNo--
toneButtonToneNo-Colour axis for the vocabulary variants.
trailingIconReact.ReactNodeNo-Icon after the label (decorative).
variantButtonVariantNo-Look. Omit for the ambient default (`glinr`, or `plain` / `glass` under `style="minimal"` / `"glass"`). Legacy names (`default`, `primary`, `secondary`, `destructive`, `frosted`, `raised`) resolve to the vocabulary.