Skip to content
weftGitHub

Button

Runs an action. Use one main button per screen, and quieter ones around it.

SourcePackage @weft-js/warpPlayground

Preview

tsx
import { Button } from '@weft-js/warp'export function Default() {  return (    <Button>Deploy</Button>  )}

Installation

npm install @weft-js/warp

Usage

tsx
import { Button } from '@weft-js/warp'<Button>Deploy</Button>

Variants

Use emphasis to set how loud the button is, from solid down to link.

tsx
import { Button } from '@weft-js/warp'export function Emphasis() {  return (    <>      <Button>Deploy</Button>      <Button emphasis="frame">Deploy</Button>      <Button emphasis="soft">Deploy</Button>      <Button emphasis="outline">Deploy</Button>      <Button emphasis="ghost">Deploy</Button>      <Button emphasis="link">Deploy</Button>    </>  )}

Sizes

Use size for sm, md or lg.

tsx
import { Button } from '@weft-js/warp'export function Sizes() {  return (    <>      <Button size="sm">Deploy</Button>      <Button>Deploy</Button>      <Button size="lg">Deploy</Button>    </>  )}

Tones

Use tone to say what the action means. critical is for anything destructive.

tsx
import { Button, Row, Stack } from '@weft-js/warp'export function Tones() {  return (    <Stack gap="related">      {TONES.map(tone => (        <Row key={tone} gap="related">          <Button tone={tone}>Deploy</Button>          <Button tone={tone} emphasis="soft">Deploy</Button>          <Button tone={tone} emphasis="outline">Deploy</Button>          <Button tone={tone} emphasis="ghost">Deploy</Button>        </Row>      ))}    </Stack>  )}const TONES = ['neutral', 'accent', 'positive', 'caution', 'critical', 'info'] as const

Fill that follows

By default the fill wipes in from the left and out to the right. Add follow and, at vivid, it spreads from where the pointer comes in instead.

tsx
import { Button } from '@weft-js/warp'export function Follow() {  return (    <>      <Button>Wipe</Button>      <Button follow>Follow</Button>      <Button follow emphasis="soft">Follow</Button>      <Button follow emphasis="outline" tone="neutral">Follow</Button>    </>  )}

Icons

Use icon to draw one before the label.

tsx
import { Button } from '@weft-js/warp'export function WithIcon() {  return (    <>      <Button icon="rocket">Deploy</Button>      <Button emphasis="outline" tone="neutral" icon="plus">New project</Button>      <Button emphasis="ghost" tone="critical" icon="trash">Delete</Button>    </>  )}

Icon only

Needs label: it becomes the accessible name.

tsx
import { Button } from '@weft-js/warp'export function IconOnly() {  return (    <>      <Button icon="plus" label="New project" />      <Button emphasis="outline" tone="neutral" icon="settings" label="Settings" />      <Button emphasis="ghost" tone="neutral" icon="trash" label="Delete" />    </>  )}

Full width

Use full to take the width of the container.

tsx
import { Button } from '@weft-js/warp'export function Full() {  return (    <Button full>Deploy to production</Button>  )}

Loading

The label stays the accessible name, presses are swallowed and focus stays put.

tsx
import { Button } from '@weft-js/warp'export function Loading() {  return (    <>      <Button loading>Deploying</Button>      <Button emphasis="outline" tone="neutral" loading>Deploying</Button>    </>  )}

Disabled

Prefer aria-disabled: the button stays focusable, so people can find out why.

tsx
import { Button } from '@weft-js/warp'export function Disabled() {  return (    <>      <Button aria-disabled="true">Deploy</Button>      <Button emphasis="outline" tone="neutral" aria-disabled="true">Deploy</Button>      <Button disabled>Deploy</Button>    </>  )}

Delete confirmation

Give the confirming button tone="critical" and the way out a quiet ghost button.

tsx
import { Button, Row, Stack, Text } from '@weft-js/warp'export function DeleteProject() {  return (    <Stack gap="related">      <Text role="heading">Delete storefront?</Text>      <Text subtle>Its 412 deploys and all its logs go too. This can't be undone.</Text>      <Row gap="related" justify="end">        <Button emphasis="ghost" tone="neutral">Keep it</Button>        <Button tone="critical">Delete project</Button>      </Row>    </Stack>  )}

Empty state

A single button with an icon gives an empty view one clear next step.

tsx
import { Button, Stack, Text } from '@weft-js/warp'export function EmptyState() {  return (    <Stack gap="related" align="center">      <Text role="heading">No projects yet</Text>      <Text subtle>Connect a repository and your first deploy runs in about a minute.</Text>      <Button icon="plus">New project</Button>    </Stack>  )}

Save with progress

Set loading while the work runs, then confirm with a toast.

tsx
import { state } from '@weft-js/core'import { Button, Toaster, toast } from '@weft-js/warp'export function SaveAsync() {  const saving = state(false)  const save = async () => {    saving.value = true    await new Promise(done => { setTimeout(done, 1500) })    saving.value = false    toast.show({ title: 'Saved', description: 'Your changes are live.' })  }  return (    <>      <Button loading={saving.value} onClick={save}>{saving.value ? 'Saving' : 'Save changes'}</Button>      <Toaster />    </>  )}

When to use

Use it for

  • The press changes something here and now
  • One main action per view, with quieter ones around it

Reach for something else

  • Link, when it goes to another page
  • Toggle, when it stays pressed
  • Menu, when there are several actions and little room

Playground

Emphasis
Tone
Size
import { Button } from '@weft-js/warp'<Button>Deploy</Button>

Accessibility

Enter
Presses the button
Space
Presses the button
  • It is a real <button>, so it works with every assistive technology and inside forms.
  • An icon-only button must have label.

API Reference

PropTypeDefaultDescription
emphasisEmphasis'solid'solid (default) · soft · outline · ghost · frame (the signature) · link. Fixed at creation: it picks the anatomy.
toneTone—What the action means. critical for anything destructive.
size"sm" | "md" | "lg"—sm, md or lg. Each follows the density around it.
fillbooleanfalseA frame button with the solid fill sweeping in.
followbooleanfalseAt vivid, the hover fill spreads from where the pointer comes in instead of wiping in from the left.
fullbooleanfalseTakes the whole width of its container.
iconIconRef—An icon before the label. Alone, it needs label.
collapse"tablet" | "phone"—With icon and text: on a phone (or a tablet and a phone) only the icon shows, and the text stays the accessible name.
labelstring—The accessible name, for an icon-only button.
loadingbooleanfalseShows a spinner, keeps the button's width and swallows presses.
childrenunknown—What it contains.
EventTypeDefaultDescription
onClick((e: MouseEvent) => unknown)—Called when pressed.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="button"] [data-part="collapse"]

collapsesweep-fillframe-cornerssweep-cornerssweep-outlinesweep-labelsweep-contentlabelspinnersweep

RelatedToggleMenuChip