HextaUI

Button

Her varyant ve boyutta düğmeler; hızlı istekler için spinner'ı atlayan yerleşik yükleme, başarı ve hata akışı.

"use client"

import { Button } from "@/components/ui/button"

function wait(ms: number) {
  return new Promise<void>((resolve) => setTimeout(resolve, ms))
}

async function fail(ms: number) {
  await wait(ms)
  throw new Error("Request failed")
}

export function ButtonDemo() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Button feedback onClick={() => wait(900)}>
        Save changes
      </Button>
      <Button feedback variant="outline" onClick={() => fail(900)}>
        Request that fails
      </Button>
      <Button feedback variant="secondary" onClick={() => wait(80)}>
        Fast request
      </Button>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/button.json

Bileşeni, HextaUI tema token'larını ve bileşenin bağımlı olduğu tüm HextaUI bileşenlerini ekler.

import { Button } from "@/components/ui/button"
<Button feedback onClick={() => saveSettings()}>
  Save changes
</Button>

feedback ile onClick'ten bir promise döndürün; düğme önce yükleniyor, sonra başarı veya hata durumunu gösterir ve kendiliğinden sıfırlanır.

Varyantlar

Yedi variant. destructive yumuşak bir tondur; böylece tehlikeli bir eylem bağırmadan net okunur. ghost-destructive ise Sign out veya Remove gibi tekrarlanan satır eylemleri için sakin sürümdür.

import { Button } from "@/components/ui/button"

export function ButtonVariants() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Button>Default</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="ghost">Ghost</Button>
      <Button variant="destructive">Destructive</Button>
      <Button variant="ghost-destructive">Ghost destructive</Button>
      <Button variant="link">Link</Button>
    </div>
  )
}

Boyutlar

xs ile lg arası metin boyutları ve kare icon-* boyutları. Küçük simge düğmeleri dokunmatik ekranlarda daha büyük, görünmez bir dokunma alanı alır.

import { IconPlus } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"

export function ButtonSizes() {
  return (
    <div className="flex flex-col items-center gap-4">
      <div className="flex flex-wrap items-center justify-center gap-2">
        <Button size="xs" variant="outline">
          Extra small
        </Button>
        <Button size="sm" variant="outline">
          Small
        </Button>
        <Button variant="outline">Default</Button>
        <Button size="lg" variant="outline">
          Large
        </Button>
      </div>
      <div className="flex flex-wrap items-center justify-center gap-2">
        <Button size="icon-xs" variant="outline" aria-label="Add">
          <IconPlus />
        </Button>
        <Button size="icon-sm" variant="outline" aria-label="Add">
          <IconPlus />
        </Button>
        <Button size="icon" variant="outline" aria-label="Add">
          <IconPlus />
        </Button>
        <Button size="icon-lg" variant="outline" aria-label="Add">
          <IconPlus />
        </Button>
        <Button size="icon-xl" variant="outline" aria-label="Add">
          <IconPlus />
        </Button>
      </div>
    </div>
  )
}

Hap (Pill)

shape="pill" uçları tamamen yuvarlar ve simge boyutları daireye dönüşür. Bir sohbet yazma alanı gibi yuvarlak yüzeylerin içindeki düğmelere uygundur.

import { IconArrowUp, IconPlus } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"

export function ButtonPill() {
  return (
    <div className="flex flex-wrap items-center gap-2">
      <Button shape="pill">Get started</Button>
      <Button shape="pill" variant="outline">
        <IconPlus data-icon="inline-start" />
        New chat
      </Button>
      <Button shape="pill" size="icon" aria-label="Send">
        <IconArrowUp />
      </Button>
      <Button shape="pill" size="icon-sm" variant="ghost" aria-label="Add">
        <IconPlus />
      </Button>
    </div>
  )
}

Simgeyle

Bir simgeyi data-icon="inline-start" veya "inline-end" ile işaretleyin; o taraftaki dolgu dengelemek için daralır.

import { IconArrowRight, IconGitBranch } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"

export function ButtonWithIcon() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Button variant="outline">
        <IconGitBranch data-icon="inline-start" />
        New branch
      </Button>
      <Button>
        Continue
        <IconArrowRight data-icon="inline-end" />
      </Button>
    </div>
  )
}

Devre dışı

focusableWhenDisabled devre dışı bir düğmeyi tab sırasında tutar; böylece bir tooltip veya açıklamaya klavyeyle yine ulaşılabilir.

import { Button } from "@/components/ui/button"

export function ButtonDisabled() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Button disabled>Disabled</Button>
      <Button variant="outline" disabled focusableWhenDisabled>
        Focusable when disabled
      </Button>
    </div>
  )
}

Özel etiketler

loadingLabel, successLabel ve errorLabel her durumun metnini değiştirir. Eski etiket dönerek çıkarken her etiket dönerek girer.

"use client"

import { IconDeviceFloppy } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"

function wait(ms: number) {
  return new Promise<void>((resolve) => setTimeout(resolve, ms))
}

async function fail(ms: number) {
  await wait(ms)
  throw new Error("Request failed")
}

export function ButtonCustomLabels() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Button
        feedback
        loadingLabel="Saving…"
        successLabel="Saved"
        errorLabel="Couldn’t save"
        onClick={() => wait(1200)}
      >
        <IconDeviceFloppy data-icon="inline-start" />
        Save
      </Button>
      <Button
        feedback
        variant="outline"
        loadingLabel="Saving…"
        successLabel="Saved"
        errorLabel="Couldn’t save"
        onClick={() => fail(1200)}
      >
        <IconDeviceFloppy data-icon="inline-start" />
        Save
      </Button>
    </div>
  )
}

Yumuşak genişlik

Düğme, en uzun etiket için yer ayırmak yerine her etiketin genişliğine yumuşakça geçer; böylece çevresindeki hiçbir şey zıplamaz.

"use client"

import { Button } from "@/components/ui/button"

function wait(ms: number) {
  return new Promise<void>((resolve) => setTimeout(resolve, ms))
}

export function ButtonSmoothWidth() {
  return (
    <Button
      feedback
      loadingLabel="Publishing to 3 regions…"
      successLabel="Live"
      onClick={() => wait(1600)}
    >
      Publish
    </Button>
  )
}

Hata ayrıntıları

Ret nedenini göstermek için errorLabel'a bir fonksiyon geçin. İşaretçi veya klavye odağı düğmede kaldığı sürece hata ekranda kalır.

"use client"

import { Button } from "@/components/ui/button"

export function ButtonErrorDetails() {
  return (
    <Button
      feedback
      variant="outline"
      errorLabel={(error) =>
        error instanceof Error ? error.message : "Failed"
      }
      onClick={async () => {
        await new Promise((resolve) => setTimeout(resolve, 700))
        throw new Error("Card declined")
      }}
    >
      Pay $24
    </Button>
  )
}

Formlar

Submit düğmeleri için onSubmit içinde useButtonFeedback'ten track() çağırın ve buttonProps'u düğmeye yayın. Hatayı görmek için @ karakterini kaldırın.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { useButtonFeedback } from "@/hooks/use-button-feedback"

function wait(ms: number) {
  return new Promise<void>((resolve) => setTimeout(resolve, ms))
}

async function fail(ms: number) {
  await wait(ms)
  throw new Error("Invalid email")
}

export function ButtonForm() {
  const save = useButtonFeedback()
  const [email, setEmail] = React.useState("[email protected]")

  return (
    <form
      className="flex w-full max-w-sm items-center gap-2"
      onSubmit={(event) => {
        event.preventDefault()
        save.track(email.includes("@") ? wait(900) : fail(600))
      }}
    >
      <input
        aria-label="Email"
        value={email}
        onChange={(event) => setEmail(event.target.value)}
        className="h-9 min-w-0 flex-1 rounded-md border border-input bg-transparent px-3 text-sm transition-shadow outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden pointer-coarse:text-touch"
      />
      <Button
        type="submit"
        {...save.buttonProps}
        successLabel="Subscribed"
        errorLabel="Invalid email"
      >
        Subscribe
      </Button>
    </form>
  )
}

Simge düğmeleri

Simge boyutları her durum için yalnızca simgeyi değiştirir ve kare şeklini korur. aria-label erişilebilir ad olarak kalır.

"use client"

import { IconDeviceFloppy } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"

function wait(ms: number) {
  return new Promise<void>((resolve) => setTimeout(resolve, ms))
}

async function fail(ms: number) {
  await wait(ms)
  throw new Error("Request failed")
}

export function ButtonIconFeedback() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Button
        feedback
        size="icon"
        variant="outline"
        aria-label="Save"
        onClick={() => wait(900)}
      >
        <IconDeviceFloppy />
      </Button>
      <Button
        feedback
        size="icon"
        variant="outline"
        aria-label="Save"
        onClick={() => fail(900)}
      >
        <IconDeviceFloppy />
      </Button>
    </div>
  )
}

Her variant üzerinde geri bildirim

Dolgulu variant'lar iş bitince yeşile veya kırmızıya döner. ghost ve link yalnızca metin renklerini değiştirir.

"use client"

import { Button } from "@/components/ui/button"

function wait(ms: number) {
  return new Promise<void>((resolve) => setTimeout(resolve, ms))
}

const variants = [
  "default",
  "outline",
  "secondary",
  "ghost",
  "destructive",
  "link",
] as const

export function ButtonVariantsFeedback() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      {variants.map((variant) => (
        <Button
          key={variant}
          feedback
          variant={variant}
          onClick={() => wait(900)}
        >
          {variant}
        </Button>
      ))}
    </div>
  )
}

Kontrollü yükleme

İş başka yerde izleniyorsa loading'i kendiniz ayarlayın. Düğme odaklanabilir kalır ve meşgul olduğunu duyurur.

"use client"

import * as React from "react"
import { IconSend } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"

export function ButtonControlledLoading() {
  const [loading, setLoading] = React.useState(false)

  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Button loading={loading} onClick={() => setLoading(true)}>
        <IconSend data-icon="inline-start" />
        Send invite
      </Button>
      <Button variant="ghost" onClick={() => setLoading(false)}>
        Stop loading
      </Button>
    </div>
  )
}

Kontrollü durum

status'u doğrudan yönetin; örneğin bir form kütüphanesinin submit durumundan.

"use client"

import * as React from "react"
import { IconRocket } from "@tabler/icons-react"

import { Button, type ButtonStatus } from "@/components/ui/button"

const statuses: ButtonStatus[] = ["idle", "loading", "success", "error"]

export function ButtonControlledStatus() {
  const [status, setStatus] = React.useState<ButtonStatus>("idle")

  return (
    <div className="flex flex-col items-center gap-3">
      <Button
        status={status}
        onStatusChange={setStatus}
        successLabel="Deployed"
        errorLabel="Deploy failed"
      >
        <IconRocket data-icon="inline-start" />
        Deploy
      </Button>
      <div className="flex flex-wrap justify-center gap-2">
        {statuses.map((next) => (
          <Button
            key={next}
            size="xs"
            variant={status === next ? "secondary" : "ghost"}
            onClick={() => setStatus(next)}
          >
            {next}
          </Button>
        ))}
      </div>
    </div>
  )
}

Düğmenin bağlantı semantiğini koruması için render'a bir anchor geçin ve nativeButton={false} ayarlayın.

import { IconArrowUpRight } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"

export function ButtonLink() {
  return (
    <Button variant="outline" nativeButton={false} render={<a href="#" />}>
      Read the docs
      <IconArrowUpRight data-icon="inline-end" />
    </Button>
  )
}

Sağdan sola

Simgeler ve durum etiketleri okuma yönünü izler.

"use client"

import { IconDeviceFloppy } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"

function wait(ms: number) {
  return new Promise<void>((resolve) => setTimeout(resolve, ms))
}

export function ButtonRtl() {
  return (
    <div dir="rtl" className="flex flex-wrap items-center justify-center gap-2">
      <Button
        feedback
        successLabel="تم الحفظ"
        errorLabel="فشل الحفظ"
        onClick={() => wait(900)}
      >
        <IconDeviceFloppy data-icon="inline-start" />
        حفظ
      </Button>
    </div>
  )
}
TuşAction
EnterSpaceDüğmeyi etkinleştirir. Bir geri bildirim isteği sürerken yok sayılır.
TabOdağı taşır. Yüklenen bir düğme odaklanabilir kalır; hata üzerine odaklanmak, siz uzaklaşana kadar onu ekranda tutar.
  • Her durum değişikliği kibar (polite) bir canlı bölge üzerinden duyurulur: önce yükleme, sonra başarı veya hata etiketi.
  • Yükleme sırasında düğme aria-busy ayarlar ve odaklanabilir kalır; böylece istek sürerken odak asla kaybolmaz.
  • Spinner yalnızca 150ms sonra görünür ve sonra en az 400ms kalır; böylece hızlı istekler asla göz kırpmaz, yavaşlar da titremez.
  • Azaltılmış harekette durum etiketleri dönmek yerine solar ve hata sarsıntısı atlanır.

Base UI button üzerine kuruludur. Bir <button> oluşturur ve onun tüm niteliklerini kabul eder.

PropTürVarsayılan
variant
"default" | "outline" | "secondary" | "ghost" | "ghost-destructive" | "destructive" | "link""default"
size
"xs" | "sm" | "default" | "lg" | "icon-xs" | "icon-sm" | "icon" | "icon-lg" | "icon-xl""default"
shape
"default" | "pill""default"
feedbackonClick'ten dönen promise'i izleyin ve durumunu gösterin.
booleanfalse
onClickGeri bildirimi yönetmek için bir promise döndürün.
(event) => unknown–
loadingKontrollü yükleme durumu.
boolean–
statusKontrollü durum. loading'e göre önceliklidir.
"idle" | "loading" | "success" | "error"–
onStatusChange
(status: ButtonStatus) => void–
onErrorRet nedeniyle çağrılır.
(error: unknown) => void–
resetAfterBoşta durumuna dönmeden önceki milisaniye.
number | { success?: number; error?: number }{ success: 2000, error: 4000 }
loadingLabelSpinner'ın yanında gösterilir. Simge boyutlarında gizlenir.
ReactNode–
successLabel
ReactNode"Done"
errorLabel
ReactNode | (error: unknown) => ReactNode"Failed"
disabled
booleanfalse
focusableWhenDisabledYükleme sırasında her zaman true.
booleanfalse
nativeButtonrender bir <button> değilse false yapın.
booleantrue
render
ReactElement | (props, state) => ReactElement<button>
ÖznitelikAçıklama
data-slot="button"CSS'te düğmeleri hedefleyin.
data-statusidle, loading, success veya error. feedback, loading ya da status kullanıldığında bulunur.
data-disabledDüğme devre dışıyken bulunur.
aria-busyYükleme sırasında bulunur.

Aynı geri bildirim akışını bir formun onSubmit'i gibi her yerden çalıştırır. resetAfter, onStatusChange ve onError kabul eder. Tüm zamanlama için useButtonFeedback kılavuzuna bakın.

DöndürürAçıklama
track(action)Bir promise veya bir promise döndüren fonksiyon geçin. Bir istek sürerken yapılan çağrılar yok sayılır.
buttonPropsDurumu göstermek ve sıfırlamayı üzerine gelince ve odakta duraklatmak için <Button> üzerine yayın.
statusGeçerli ButtonStatus.
errorSon ret nedeni.
reset()İsteği iptal eder ve boşta (idle) durumuna döner.
isPending()Bir isteğin sürüp sürmediği.

Kullanıldığı bloklar

Button üzerine kurulan bloklar.