suiss/ui
  • Docs
  • Components
  • Patterns
  • Interactions
  • Charts
  • Blocks
GitHubGet started
DocsComponentsPatternsInteractionsChartsBlocks
Components82
  • Overview
  • Accordion
  • Alert
  • Alert Dialog
  • Alert Sheet
  • Amount Input
  • Aspect Ratio
  • Attachment
  • Avatar
  • Badge
  • Bottom Bar
  • Breadcrumb
  • Bubble
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Chip
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • FAB
  • Field
  • Fullscreen Menu
  • Haptics
  • Header
  • Hover Card
  • Icon
  • Input
  • Input Accessory Bar
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Keyboard Scroll View
  • Keypad
  • Label
  • Marker
  • Menubar
  • Message
  • Message Scroller
  • Native Select
  • Navigation Menu
  • Page Indicator
  • Pagination
  • Pin Pad
  • Popover
  • Progress
  • QR Scanner
  • Questionnaire
  • Radio Group
  • Resizable
  • Screen
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Signature Pad
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Tab Bar
  • Table
  • Tabs
  • Text
  • Textarea
  • Toast
  • Toggle
  • Toggle Group
  • Tooltip
  • Wheel Picker
  • Widget

suiss/ui · Component

Button

A pill-shaped button that triggers an action, with variants for every level of emphasis.

Button is an open-source component from suiss/ui for React, React Native, SwiftUI and Jetpack Compose. The @esuiss/ui CLI copies its source into your project, so you own the code.

Button renders a native button by default. It supports eight visual variants, five text sizes and five icon-only sizes, and an optional block mode that fills the container width.

Use the default variant for the primary action in a view, secondary, tinted or outline for supporting actions, ghost and plain for low-emphasis actions, and destructive for actions that delete or remove. To join several buttons into one control, use Button Group.

Installation

CLI

$ pnpm dlx shadcn@latest add @esuiss/button

Works in any project with a components.json. See Installation for the one-line registry setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add button

Manual

  1. Install the dependencies and add the cn helper:
$ pnpm add @base-ui/react class-variance-authority
  1. Copy the source into components/ui/button.tsx:
  1. Update the import paths to match your project.

Usage

import { Button } 
<Button

Mark icons with data-icon="inline-start" or data-icon="inline-end" to tighten the padding on that side. Icons without a size class are sized automatically.

Use an icon size, such as icon or icon-sm, for buttons that contain only an icon, and give them an aria-label.

To render a link or a router component with button styling, use the render prop from Base UI. buttonVariants is also exported if you need the classes on another element.

To render a link or a router component with button styling, set asChild and pass it as the child. buttonVariants is also exported if you need the classes on another element.

Button always renders a button. To give a link or a router component button styling, apply the classes from buttonVariants to it.

Examples

Variants

Shows all eight variants side by side.

Sizes

Compares the xs, sm, default and lg sizes.

With icon

Adds leading and trailing icons, and an icon-only button.

Disabled

A disabled button that is skipped in the tab order.

Block

Full-width buttons at each size, whose corner radius scales with size.

API Reference

01

Button

The interactive button element.

PropTypeDefaultDescription
variant"default" | "outline" | "secondary" | "tinted" | "ghost" | "plain" | "destructive" | "link""default"Visual style: default, outline, secondary, tinted, ghost, plain, destructive or link.
size"default" | "xs" | "sm" | "lg" | "xl" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | "icon-xl""default"Height and padding. xs to xl for text buttons; icon-xs to icon-xl for square icon-only buttons.
blocktrue-When true, the button fills the container width and uses rounded corners instead of a pill shape.

Also accepts the props of the primitive part it wraps.

Accessibility

  • Renders a native button, so Enter and Space activate it.
  • A disabled button is removed from the tab order.
  • Icon-only buttons need an aria-label.
  • Keyboard focus shows a 3px ring in --ring; the destructive variant tints it with ring-destructive/20.
  • aria-invalid adds a destructive border and ring, and aria-expanded shows a selected background on outline, ghost and plain buttons that open menus or popovers.

Notes

  • Styling hook: data-slot="button".
  • Colors come from the primary, secondary, accent and destructive variables. Hover uses the same color with an opacity, such as hover:bg-primary/80, and disabled buttons use opacity-50.
  • Buttons in a group raise the focused one above its neighbors so the whole ring stays visible.

Button is a Pressable with the button role. It supports eight visual variants, five text sizes and five icon-only sizes, and an optional block mode that fills the width of its container.

Use the default variant for the primary action on a screen, secondary, tinted or outline for supporting actions, ghost and plain for low-emphasis actions, and destructive for actions that delete or remove. To join several buttons into one control, use Button Group.

Installation

$ pnpm dlx shadcn@latest add @esuiss-rn/button

See Installation for the components.json setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add button
$ pnpm dlx @esuiss/ui@latest add button --platform swift
NextButton GroupPreviousBubble

On this page

  1. 01Installation
    • CLI
    • Manual
  2. 02Usage
  3. 03Examples
    • Variants
    • Sizes
    • With icon
    • Disabled
    • Block
  4. 04API Reference
    • Button
  5. 05Accessibility
  6. 06Notes
  7. 07Installation
    • Manual
  8. 08Usage
  9. 09Examples
    • Variants
    • Sizes
    • With icon
    • Disabled
    • Block
  10. 10API Reference
    • Button
  11. 11Accessibility
  12. 12Notes
from
"@/components/ui/button"
>Button</
Button
>
$ pnpm dlx @esuiss/ui@latest add button --platform kotlin

The CLI also adds what it builds on: Icon, Text.

Manual

  1. Install the dependencies:
npx expo install class-variance-authority cn
  1. Add the pieces it builds on: Icon, Text.

  2. Copy the source into components/ui/button.tsx:

  1. Update the import paths to match your project.
  1. Install the dependencies:
npx expo install class-variance-authority cn
  1. Add the pieces it builds on: Icon, Text.

  2. Copy the source into components/ui/button.tsx:

  1. Update the import paths to match your project.
  1. Add the pieces it builds on: Icon, Text.

  2. Copy the source into components/ui/button.tsx:

  1. Update the import paths to match your project.
  1. Target iOS 17 or later.

  2. Copy the theme files every view uses:

swift/Theme/Theme.swift
import SwiftUI
import UIKit

struct RGBA: Sendable {
  let red: CGFloat
  let green: CGFloat
  let blue: CGFloat
  let alpha: CGFloat

  init(_ hex: UInt32, _ alpha: CGFloat = 1) {
    red = CGFloat((hex >> 16) & 0xFF) / 255
    green = CGFloat((hex >> 8) & 0xFF) / 255
    blue = CGFloat(hex & 0xFF) / 255
    self.alpha = alpha
  }
swift/Theme/Tokens.swift
import SwiftUI

public enum EsuissColor {
  public static let background = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))
  public static let foreground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let card = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
  public static let cardForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let popover = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
  public static let popoverForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let primary = Color.adaptive(light: .init(0x0071E3), dark: .init(0x2997FF))
  public static let primaryForeground = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0xFFFFFF))
  public static let secondary = Color.adaptive(light: .init(0x000000, 0.04), dark: .init(0xFFFFFF, 0.1))
  public static let secondaryForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let muted = Color.adaptive(light: .init(0xF5F5F7), dark: .init(0x161617))
  public static let mutedForeground = Color.adaptive(light: .init(0x6E6E73), dark: .init(0xA1A1A6))
  public static let accent = Color.adaptive(light: .init(0x000000, 0.06), dark: .init(0xFFFFFF, 0.1))
  1. Copy the source and every file it uses:
swift/Components/Button.swift
import SwiftUI

public struct VariantButtonStyle: ButtonStyle {
  public enum Variant: CaseIterable, Sendable {
    case `default`, outline, secondary, tinted, ghost, plain, destructive, link
  }

  public enum Size: CaseIterable, Sendable {
    case `default`, xs, sm, lg, xl, icon, iconXs, iconSm, iconLg, iconXl

    var height: CGFloat {
      switch self {
      case .xs, .iconXs: 24
      case .sm, .iconSm: 32
      case .default, .icon: 36
      case .lg, .iconLg: 44
swift/Components/ButtonGroup.swift
import SwiftUI

extension EnvironmentValues {
  @Entry var buttonGroup: Axis? = nil
  @Entry var buttonGroupSize: CGSize = .zero
}

enum ButtonGroupSpace: Hashable {
  case group
}

struct ButtonGroupFrame {
  let bounds: Anchor<CGRect>
  let outlined: Bool
}
swift/Components/TextVariant.swift
import SwiftUI

public enum TextVariant: CaseIterable, Sendable {
  case `default`, h1, h2, h3, h4, p, blockquote, code, lead, large, small, muted

  var style: TextStyle {
    switch self {
    case .h1: .xl3
    case .h2: .xl2
    case .h3: .xl
    case .h4, .lead, .large: .lg
    case .code, .small, .muted: .sm
    default: .base
    }
  }
  1. Add the files to your app target.
  1. Set minSdk to 24 or higher and add the dependencies:
build.gradle.kts
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")
  1. Copy the theme files every view uses:
kotlin/theme/Theme.kt
package com.esuiss.ui.theme

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.shadow.Shadow as DropShadow
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
kotlin/theme/Tokens.kt
package com.esuiss.ui.theme

import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

@Immutable
data class Colors(
    val background: Color,
    val foreground: Color,
    val card: Color,
    val cardForeground: Color,
    val popover: Color,
    val popoverForeground: Color,
    val primary: Color,
    val primaryForeground: Color,
  1. Copy the source and every file it uses:
kotlin/components/Content.kt
package com.esuiss.ui.components

import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

val LocalContentStyle = compositionLocalOf { TextStyle.Default }

val LocalContentColor = compositionLocalOf { Color.Unspecified }

val LocalIconSize = compositionLocalOf { 20.dp }
kotlin/components/Icon.kt
package com.esuiss.ui.components

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
import com.esuiss.ui.theme.EsuissTheme

@Composable
fun Icon(
    imageVector: ImageVector,
  1. Update the com.esuiss.ui package names to match your project.

Usage

import { Button } from "@/components/ui/button"
<View className="items-start">
  <Button>
    <Text>Button</Text>
  </Button>
</View>

Put the label in a Text and icons in an Icon. The button passes its text color and icon size down through context, so children pick them up without extra classes.

Use an icon size, such as icon or icon-sm, for buttons that contain only an icon, and give them an accessibilityLabel.

buttonVariants and buttonTextVariants are also exported if you need the classes on another element.

Examples

Variants

All eight variants, plus an icon-only button.

Web

Web preview

import { PlusIcon } from "phosphor-react-native"
import { View } from "react-native"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Text } from "@/components/ui/text"

export function ButtonVariantsExample() {
  return (
    <View className="gap-3">
      <Button>
        <Text>Default</Text>
      </Button>
      <Button variant="secondary">
        <Text>Secondary</Text>
      </Button>
      <Button variant="tinted">
        <Text>Tinted</Text>
      </Button>
      <Button variant="outline">
        <Text>Outline</Text>
      </Button>
      <Button variant="ghost">
        <Text>Ghost</Text>
      </Button>
      <Button variant="plain">
        <Text>Plain</Text>
      </Button>
      <Button variant="destructive">
        <Text>Destructive</Text>
      </Button>
      <Button variant="link">
        <Text>Link</Text>
      </Button>
      <Button size="icon" variant="outline">
        <Icon as={PlusIcon} />
      </Button>
    </View>
  )
}
import EsuissUI
import PhosphorSwift
import SwiftUI

struct ButtonVariantsExample: View {
  var body: some View {
    VStack(alignment: .leading, spacing: 12) {
      WideButton(title: "Default", variant: .default)
      WideButton(title: "Secondary", variant: .secondary)
      WideButton(title: "Tinted", variant: .tinted)
      WideButton(title: "Outline", variant: .outline)
      WideButton(title: "Ghost", variant: .ghost)
      WideButton(title: "Plain", variant: .plain)
      WideButton(title: "Destructive", variant: .destructive)
      WideButton(title: "Link", variant: .link)
      IconButton(icon: Ph.plus.regular, size: .icon, variant: .outline)
    }
  }
}

struct WideButton: View {
  let title: LocalizedStringKey
  let variant: VariantButtonStyle.Variant

  var body: some View {
    Button {} label: {
      Text(title).frame(maxWidth: .infinity)
    }
    .buttonStyle(.variant(variant))
  }
}

struct IconButton<Icon: View>: View {
  let icon: Icon
  let size: VariantButtonStyle.Size
  let variant: VariantButtonStyle.Variant

  var body: some View {
    Button {} label: {
      icon.frame(width: size.iconSize, height: size.iconSize)
    }
    .buttonStyle(.variant(variant, size: size))
    .accessibilityLabel(Text("Add"))
  }
}
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.Plus
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonSize
import com.esuiss.ui.components.ButtonVariant
import com.esuiss.ui.components.Icon

@Composable
fun ButtonVariants() {
    val fill = Modifier.fillMaxWidth()
    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
        Button("Default", onClick = {}, modifier = fill)
        Button("Secondary", onClick = {}, modifier = fill, variant = ButtonVariant.Secondary)
        Button("Tinted", onClick = {}, modifier = fill, variant = ButtonVariant.Tinted)
        Button("Outline", onClick = {}, modifier = fill, variant = ButtonVariant.Outline)
        Button("Ghost", onClick = {}, modifier = fill, variant = ButtonVariant.Ghost)
        Button("Plain", onClick = {}, modifier = fill, variant = ButtonVariant.Plain)
        Button("Destructive", onClick = {}, modifier = fill, variant = ButtonVariant.Destructive)
        Button("Link", onClick = {}, modifier = fill, variant = ButtonVariant.Link)
        Button(onClick = {}, variant = ButtonVariant.Outline, size = ButtonSize.Icon) {
            Icon(PhosphorIcons.Regular.Plus, contentDescription = "Add")
        }
    }
}

Sizes

Compares the text and icon-only sizes.

Web

Web preview

import { PlusIcon } from "phosphor-react-native"
import { View } from "react-native"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Text } from "@/components/ui/text"

export function ButtonSizesExample() {
  return (
    <View className="gap-3">
      <View className="flex-row flex-wrap items-center gap-3">
        <Button size="xs">
          <Text>Extra small</Text>
        </Button>
        <Button size="sm">
          <Text>Small</Text>
        </Button>
        <Button>
          <Text>Default</Text>
        </Button>
        <Button size="lg">
          <Text>Large</Text>
        </Button>
        <Button size="xl">
          <Text>Extra large</Text>
        </Button>
      </View>
      <View className="flex-row flex-wrap items-center gap-3">
        <Button size="icon-xs" variant="secondary">
          <Icon as={PlusIcon} />
        </Button>
        <Button size="icon-sm" variant="secondary">
          <Icon as={PlusIcon} />
        </Button>
        <Button size="icon" variant="secondary">
          <Icon as={PlusIcon} />
        </Button>
        <Button size="icon-lg" variant="secondary">
          <Icon as={PlusIcon} />
        </Button>
        <Button size="icon-xl" variant="secondary">
          <Icon as={PlusIcon} />
        </Button>
      </View>
    </View>
  )
}
import EsuissUI
import PhosphorSwift
import SwiftUI

struct ButtonSizesExample: View {
  var body: some View {
    VStack(alignment: .leading, spacing: 12) {
      FlowRow {
        Button("Extra small") {}.buttonStyle(.variant(size: .xs))
        Button("Small") {}.buttonStyle(.variant(size: .sm))
        Button("Default") {}.buttonStyle(.variant())
        Button("Large") {}.buttonStyle(.variant(size: .lg))
        Button("Extra large") {}.buttonStyle(.variant(size: .xl))
      }
      FlowRow {
        IconButton(icon: Ph.plus.regular, size: .iconXs, variant: .secondary)
        IconButton(icon: Ph.plus.regular, size: .iconSm, variant: .secondary)
        IconButton(icon: Ph.plus.regular, size: .icon, variant: .secondary)
        IconButton(icon: Ph.plus.regular, size: .iconLg, variant: .secondary)
        IconButton(icon: Ph.plus.regular, size: .iconXl, variant: .secondary)
      }
    }
  }
}

struct IconButton<Icon: View>: View {
  let icon: Icon
  let size: VariantButtonStyle.Size
  let variant: VariantButtonStyle.Variant

  var body: some View {
    Button {} label: {
      icon.frame(width: size.iconSize, height: size.iconSize)
    }
    .buttonStyle(.variant(variant, size: size))
    .accessibilityLabel(Text("Add"))
  }
}
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.Plus
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonSize
import com.esuiss.ui.components.ButtonVariant
import com.esuiss.ui.components.Icon

@Composable
fun ButtonSizes() {
    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
        FlowRow(
            horizontalArrangement = Arrangement.spacedBy(12.dp),
            verticalArrangement = Arrangement.spacedBy(12.dp),
            itemVerticalAlignment = Alignment.CenterVertically,
        ) {
            Button("Extra small", onClick = {}, size = ButtonSize.Xs)
            Button("Small", onClick = {}, size = ButtonSize.Sm)
            Button("Default", onClick = {})
            Button("Large", onClick = {}, size = ButtonSize.Lg)
            Button("Extra large", onClick = {}, size = ButtonSize.Xl)
        }
        FlowRow(
            horizontalArrangement = Arrangement.spacedBy(12.dp),
            verticalArrangement = Arrangement.spacedBy(12.dp),
            itemVerticalAlignment = Alignment.CenterVertically,
        ) {
            listOf(ButtonSize.IconXs, ButtonSize.IconSm, ButtonSize.Icon, ButtonSize.IconLg, ButtonSize.IconXl).forEach { size ->
                Button(onClick = {}, variant = ButtonVariant.Secondary, size = size) {
                    Icon(PhosphorIcons.Regular.Plus, contentDescription = "Add")
                }
            }
        }
    }
}

With icon

Leading and trailing icons, and an icon-only button.

Web

Web preview

import { ArrowRightIcon, DownloadSimpleIcon, TrashIcon } from "phosphor-react-native"
import { View } from "react-native"
import { Button } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Text } from "@/components/ui/text"

export function ButtonWithIconExample() {
  return (
    <View className="items-start gap-3">
      <Button>
        <Icon as={DownloadSimpleIcon} />
        <Text>Download</Text>
      </Button>
      <Button variant="outline">
        <Text>Continue</Text>
        <Icon as={ArrowRightIcon} />
      </Button>
      <Button variant="destructive">
        <Icon as={TrashIcon} />
        <Text>Delete</Text>
      </Button>
    </View>
  )
}
import EsuissUI
import PhosphorSwift
import SwiftUI

struct ButtonWithIconExample: View {
  var body: some View {
    VStack(alignment: .leading, spacing: 12) {
      Button {} label: {
        Label {
          Text("Download")
        } icon: {
          Ph.downloadSimple.regular
        }
      }
      .buttonStyle(.variant())
      Button {} label: {
        HStack(spacing: 6) {
          Text("Continue")
          Ph.arrowRight.regular
            .frame(width: VariantButtonStyle.Size.default.iconSize, height: VariantButtonStyle.Size.default.iconSize)
        }
      }
      .buttonStyle(.variant(.outline))
      Button {} label: {
        Label {
          Text("Delete")
        } icon: {
          Ph.trash.regular
        }
      }
      .buttonStyle(.variant(.destructive))
    }
  }
}
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.ArrowRight
import com.adamglin.phosphoricons.regular.DownloadSimple
import com.adamglin.phosphoricons.regular.Trash
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonVariant
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Text

@Composable
fun ButtonWithIcon() {
    Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
        Button(onClick = {}) {
            Icon(PhosphorIcons.Regular.DownloadSimple, contentDescription = null)
            Text("Download")
        }
        Button(onClick = {}, variant = ButtonVariant.Outline) {
            Text("Continue")
            Icon(PhosphorIcons.Regular.ArrowRight, contentDescription = null)
        }
        Button(onClick = {}, variant = ButtonVariant.Destructive) {
            Icon(PhosphorIcons.Regular.Trash, contentDescription = null)
            Text("Delete")
        }
    }
}

Disabled

Disabled buttons in every variant.

Web

Web preview

import { View } from "react-native"
import { Button } from "@/components/ui/button"
import { Text } from "@/components/ui/text"

export function ButtonDisabledExample() {
  return (
    <View className="items-start gap-3">
      <Button disabled>
        <Text>Default</Text>
      </Button>
      <Button disabled variant="secondary">
        <Text>Secondary</Text>
      </Button>
      <Button disabled variant="tinted">
        <Text>Tinted</Text>
      </Button>
      <Button disabled variant="outline">
        <Text>Outline</Text>
      </Button>
      <Button disabled variant="destructive">
        <Text>Destructive</Text>
      </Button>
    </View>
  )
}
import EsuissUI
import SwiftUI

struct ButtonDisabledExample: View {
  var body: some View {
    VStack(alignment: .leading, spacing: 12) {
      Button("Default") {}.buttonStyle(.variant())
      Button("Secondary") {}.buttonStyle(.variant(.secondary))
      Button("Tinted") {}.buttonStyle(.variant(.tinted))
      Button("Outline") {}.buttonStyle(.variant(.outline))
      Button("Destructive") {}.buttonStyle(.variant(.destructive))
    }
    .disabled(true)
  }
}
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonVariant

@Composable
fun ButtonDisabled() {
    Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
        Button("Default", onClick = {}, enabled = false)
        Button("Secondary", onClick = {}, variant = ButtonVariant.Secondary, enabled = false)
        Button("Tinted", onClick = {}, variant = ButtonVariant.Tinted, enabled = false)
        Button("Outline", onClick = {}, variant = ButtonVariant.Outline, enabled = false)
        Button("Destructive", onClick = {}, variant = ButtonVariant.Destructive, enabled = false)
    }
}

Block

Full-width buttons at each size, whose corner radius scales with size.

Web

Web preview

import { View } from "react-native"
import { Button } from "@/components/ui/button"
import { Text } from "@/components/ui/text"

export function ButtonBlockExample() {
  return (
    <View className="gap-3">
      <Button block size="lg">
        <Text>Continue</Text>
      </Button>
      <Button block size="lg" variant="secondary">
        <Text>Not now</Text>
      </Button>
      <Button block variant="tinted">
        <Text>Learn more</Text>
      </Button>
    </View>
  )
}
import EsuissUI
import SwiftUI

struct ButtonBlockExample: View {
  var body: some View {
    VStack(spacing: 12) {
      Button("Continue") {}.buttonStyle(.variant(size: .lg, block: true))
      Button("Not now") {}.buttonStyle(.variant(.secondary, size: .lg, block: true))
      Button("Learn more") {}.buttonStyle(.variant(.tinted, block: true))
    }
  }
}
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonSize
import com.esuiss.ui.components.ButtonVariant

@Composable
fun ButtonBlock() {
    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
        Button("Continue", onClick = {}, size = ButtonSize.Lg, block = true)
        Button("Not now", onClick = {}, size = ButtonSize.Lg, variant = ButtonVariant.Secondary, block = true)
        Button("Learn more", onClick = {}, variant = ButtonVariant.Tinted, block = true)
    }
}

API Reference

01

Button

PropTypeDefaultDescription
variant"default" | "outline" | "secondary" | "tinted" | "ghost" | "plain" | "destructive" | "link""default"Visual style.
size"xs" | "sm" | "default" | "lg" | "xl" | "icon-xs" | "icon-sm" | "icon" | "icon-lg" | "icon-xl""default"Height and padding; icon sizes are square.
blockboolean-Stretches the button to the full width of its container.

Also accepts the props of ButtonProps.

Accessibility

  • Renders a Pressable with role="button", so VoiceOver and TalkBack announce it as a button.
  • disabled is announced and blocks presses.
  • Icon-only buttons need an accessibilityLabel.

Notes

  • Colors come from semantic tokens such as bg-primary, bg-secondary, bg-primary/10 dark:bg-primary/20 and bg-destructive/10 dark:bg-destructive/20, with opacity-80 while pressed and opacity-50 when disabled.
  • Light and dark colors follow the system appearance.
Web

Web preview

import { View } from "react-native"
import { Button } from "@/components/ui/button"
import { Text } from "@/components/ui/text"

export function ButtonExample() {
  return (
    <View className="items-start">
      <Button>
        <Text>Button</Text>
      </Button>
    </View>
  )
}
import EsuissUI
import SwiftUI

struct ButtonExample: View {
  var body: some View {
    Button("Button") {}
      .buttonStyle(.variant())
  }
}
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import com.esuiss.ui.components.Button

@Composable
fun ButtonDefault() {
    Column {
        Button("Button", onClick = {})
    }
}
import { Button } from "@/components/ui/button"

export default function ButtonDemo() {
  return <Button>Button</Button>
}
components/ui/button.tsx
import { Button as ButtonPrimitive } from "@base-ui/react/button"
import {
  type CxOptions,
  cva,
  cx,
  type VariantProps,
} from "class-variance-authority"
import { cn } from "@/lib/utils"

const buttonStyles = cva(
  "inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full border border-transparent bg-clip-padding text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
  {
    variants: {
      variant: {
        default:
          "bg-primary text-primary-foreground hover:bg-primary/80 disabled:opacity-50",
import { Button } from "@/components/ui/button"

export default function ButtonVariants() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button variant="default">Default</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="tinted">Tinted</Button>
      <Button variant="ghost">Ghost</Button>
      <Button variant="plain">Plain</Button>
      <Button variant="destructive">Destructive</Button>
      <Button variant="link">Link</Button>
    </div>
  )
}
import { ArrowRightIcon, PlusIcon } from "@phosphor-icons/react"
import { Button } from "@/components/ui/button"

export default function ButtonWithIcon() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button>
        <PlusIcon data-icon="inline-start" />
        New project
      </Button>
      <Button variant="outline">
        Continue
        <ArrowRightIcon data-icon="inline-end" />
      </Button>
      <Button size="icon" aria-label="Add">
        <PlusIcon />
      </Button>
    </div>
  )
}
import { Button } from "@/components/ui/button"

export default function ButtonDisabled() {
  return <Button disabled>Button</Button>
}
import { Button } from "@/components/ui/button"

export default function ButtonBlock() {
  return (
    <div className="flex w-80 flex-col gap-3">
      <Button block size="xs">
        Add to Bag
      </Button>
      <Button block size="sm">
        Add to Bag
      </Button>
      <Button block>Add to Bag</Button>
      <Button block size="lg">
        Add to Bag
      </Button>
      <Button block size="xl">
        Add to Bag
      </Button>
      <Button block variant="secondary">
        Continue
      </Button>
    </div>
  )
}
import { Button } from "@/components/ui/button"

export default function ButtonSizes() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button size="xs">Extra small</Button>
      <Button size="sm">Small</Button>
      <Button size="default">Default</Button>
      <Button size="lg">Large</Button>
    </div>
  )
}
uniwind/components/ui/button.tsx
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import * as React from "react"
import { Pressable } from "react-native"
import { IconClassContext } from "@/components/ui/icon"
import { TextClassContext } from "@/components/ui/text"

const buttonVariants = cva(
  "shrink-0 flex-row items-center justify-center rounded-full border border-transparent",
  {
    variants: {
      variant: {
        default: "bg-primary active:opacity-80 disabled:opacity-50",
        outline:
          "border-border bg-background active:opacity-80 disabled:opacity-50 dark:bg-transparent",
        secondary: "bg-secondary active:opacity-80 disabled:opacity-50",
nativewind/components/ui/button.tsx
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import * as React from "react"
import { Pressable } from "react-native"
import { IconClassContext } from "@/components/ui/icon"
import { TextClassContext } from "@/components/ui/text"

const buttonVariants = cva(
  "shrink-0 flex-row items-center justify-center rounded-full border border-transparent",
  {
    variants: {
      variant: {
        default: "bg-primary active:opacity-80 disabled:opacity-50",
        outline:
          "border-border bg-background active:opacity-80 disabled:opacity-50 dark:bg-transparent",
        secondary: "bg-secondary active:opacity-80 disabled:opacity-50",
kotlin/components/Button.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
kotlin/components/ButtonGroup.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutable
stylesheet/components/ui/button.tsx
import * as React from "react"
import {
  Pressable,
  type PressableStateCallbackType,
  type StyleProp,
  StyleSheet,
  type TextStyle,
  type ViewStyle,
} from "react-native"
import { IconSizeContext } from "@/components/ui/icon"
import { TextStyleContext } from "@/components/ui/text"
import {
  fontSize,
  radius,
  type ThemeColors,
  useTheme,