Skip to content
buttery

v1.0.0

Source on GitHub

Version 1.0.0

Presets

The motion primitives that apply buttery's rules for you, from presence and morphing to reveals and loops.

The motion presets are primitives that animate one kind of change the buttery way, with the right token and reduced motion built in. Reach for a preset before writing an animation of your own, and most screens need no motion code at all.

Which one

  • Presence

    For something that appears and leaves, with a fade, a rise or a pop.

  • Morph

    For a container whose height follows its content.

  • SharedIndicator

    For a highlight that slides between options, such as a tab's underline.

  • Reveal and Stagger

    For content entering the first time it scrolls into view.

  • Loop

    For a demo that cycles its states quietly, pausing offscreen and with reduced motion.

  • buttery elements

    For an element of your own, through the enter and layout props of buttery.div and the other buttery elements.

enter and layout use the same presets, and turn them off with reduced motion. This buttery.div rises in when it mounts:

tsx

import type { ReactElement } from "react";
import { buttery } from "@butteryui/react";

export function Notice(): ReactElement {
  return (
    <buttery.div surface="card" pad={4} enter="rise">
      Your export is ready.
    </buttery.div>
  );
}

Presence

When to use it

Use it for

  • Content that appears and disappears: a saved note, a hint, extra options
  • Anything that needs an exit animation before it unmounts

Not for

  • Swapping one piece of content for another in place: use Morph
  • Sections arriving as the page scrolls: use Reveal or Stagger
  • Selection indicators: use SharedIndicator
  • Toasts: call toast() from @butteryui/react

Examples

More options

Extra options that rise in under the button that shows them, and fade as they go.

Saved note

A note that rises in after saving and fades out a moment later.

Variants and states

Presets

Each preset arrives its own way and leaves with a 120ms fade. Turn on reduced motion to see the 100ms fade that replaces them all.

Props

Presence

PropTypeDefaultDescription
show

required

booleanWhether the content is shown. Turning it off plays the exit before the content unmounts.
preset"fade" | "rise" | "pop" | "toast" | "menu" | "dialog" | "scrim""fade"How the content arrives. Defaults to "fade".

It also takes MotionElementProps<"div">.

Accessibility

No role of its own. When the change needs announcing, keep a live region (role="status") mounted outside the Presence: a region that mounts along with its content often goes unannounced. With reduced motion it only fades, in 100ms.

It takes no keyboard input of its own.

Motion

It appears with a `Presence` preset and leaves faster than it came, in about 60% of the time.

With reduced motion: Every preset becomes a 100ms fade, in and out, without moving or scaling.

Layout

While shown, its content renders in a div as wide as its parent. Once its exit has played it unmounts and takes no room, so what follows moves up into its place. It has no hit area of its own.

Morph

When to use it

Use it for

  • Content that changes inside the same panel: a fill type switching the swatches, an onboarding step's text
  • A section that grows or shrinks when its content changes

Not for

  • Content that appears and disappears on its own: use Presence
  • Moving an element between places: use Motion layout or SharedIndicator
  • Animating width: only height is a sanctioned size animation

Examples

Details panel

A panel that grows to show more details and shrinks back, with no jump.

Spring launch

Step text

Step text that crossfades in place while its block grows or shrinks to fit.

Soften the butter

Leave it out for half an hour, until a spoon slides in.

Variants and states

Swapping content

Two rows of swatches collapse into one: the panel's height follows with spring.smooth while the swatches crossfade in 120ms.

Props

Morph

PropTypeDefaultDescription
contentKeystring | numberIdentifies the content, such as the selected tab. Changing it crossfades the old content out and the new content in while the height follows.
disclosurebooleanfalseOpens on spring.snappy and closes over dur-fast, as a disclosure such as a sidebar group does, instead of following the content on spring.smooth both ways. Defaults to false.

It also takes MotionElementProps<"div">.

Accessibility

No role of its own. Swapped-out content takes focus with it when it leaves, so after a swap the user started, move focus to the new content. Keep a live region (role="status") outside the Morph when the swap needs announcing.

It takes no keyboard input of its own.

Motion

It changes shape as one surface, on `spring.smooth`, or `spring.morph` for a big change, rather than closing and reopening.

With reduced motion: Its height jumps to fit at once, and swapped content crossfades in 100ms.

Layout

A block as wide as its parent whose height follows its content. Between changes it sits at its natural height, so text that rewraps never spills, and it clips its content only while the height moves or taller content fades out. It has no hit area of its own.

SharedIndicator

When to use it

Use it for

  • The active item in a set that shares one track: segmented controls, tabs, numbered steps, an anchor grid
  • Any highlight that should travel between peers

Not for

  • Items that each draw their own surface: the indicator would cross the gaps between them, so put the items in one track
  • Content swapping in place: use Morph
  • Something appearing or disappearing: use Presence

Examples

Step picker

Numbered steps in one track; the highlight travels to the step you pick, which says it's the current one.

Step 1 of 4: Pick a size

View switch

A view switch in one track whose highlight slides to the view you pick, with the arrow keys moving the choice.

The slide as people will see it.

Variants and states

Segmented selection

The white pill slides inside one track to the item you pick with spring.snappy; arrow keys move it, and with reduced motion it moves at once.

Props

SharedIndicator

PropTypeDefaultDescription
active

required

booleanWhether this item shows the indicator. Exactly one item in a group should be active.
asChildbooleanRender the single child element (such as the item's button) instead of a span, with the indicator drawn inside it. Through a chain of asChild components, such as <RadioGroup.Item asChild> or <Surface asChild>, the indicator goes inside the element the chain renders.

It also takes ComponentPropsWithRef<"span">.

SharedIndicator.Group

PropTypeDefaultDescription
childrenReactNodeThe items, each with a SharedIndicator inside or around it.

Accessibility

Decorative: the indicator is aria-hidden, so the item itself has to say it's selected (aria-checked, aria-selected or aria-current). It moves at once with reduced motion.

It takes no keyboard input of its own.

Motion

Its selection indicator slides to the chosen option on `spring.snappy`, inside one track.

With reduced motion: The indicator appears on the new item at once instead of sliding.

Layout

Each item keeps its own size. The indicator is drawn inside the active item, filling it under its content with the item's corners, so it adds no size and never catches presses. It crosses the space between items as it slides, so put them in one track.

Reveal

When to use it

Use it for

  • Page sections below the fold: a bento grid, a stats strip, a feature list
  • Anything that should arrive once as the reader reaches it

Not for

  • Content above the fold or in app UI the user is already looking at
  • A group whose items should arrive one by one: use Stagger
  • Content that comes and goes: use Presence

Examples

Quote reveal

A quote that fades in once as it scrolls into view.

We built the whole launch deck in an afternoon.
A product team in Lisbon

Section reveal

A page section that rises into place the first time the reader reaches it.

Every deck, one click from presenting

Write the slides, pick a theme, and present from any screen.

Variants and states

Scroll into view

Each card waits below the fold and rises in with spring.gentle when 15% of it is visible. Cards already on screen when the page loads only fade.

Templates

Arrives once, the first time you scroll to it.

Themes

Arrives once, the first time you scroll to it.

Exports

Arrives once, the first time you scroll to it.

Props

Reveal

It also takes ComponentPropsWithRef<"div">.

Accessibility

Content stays in the accessibility tree while hidden, so screen readers reach it in order. With reduced motion it shows at once.

It takes no keyboard input of its own.

Motion

It appears with a `Presence` preset and leaves faster than it came, in about 60% of the time.

With reduced motion: It shows at once, with no fade or lift.

Layout

A div as wide as its parent that takes its full size from the start: while it waits it's only transparent, never offset, so it neither pushes anything around nor spills out of its container. It has no hit area of its own.

Stagger

When to use it

Use it for

  • Arrivals: the cards of a bento grid, the parts of a hero visual, a list of features
  • A group whose items should land in order, under 900ms in all

Not for

  • A single block: use Reveal
  • Lists the user edits or scrolls through often: they should just be there
  • Content that comes and goes: use Presence

Examples

Feature grid

A landing page's features, stacked when narrow and three across from 640px, arriving one after another.

Templates

Start from a layout that already works.

Themes

Your colors and fonts on every slide.

Exports

A PDF of the deck or a PNG of each slide.

Step list

A numbered list whose steps land in order.

  1. Soften the butter
  2. Cream it with sugar
  3. Fold in the flour
  4. Bake until golden

Variants and states

Arriving cards

The cards rise in 50ms apart the first time the grid is in view; the whole entrance stays under 900ms.

Templates

Themes

Pictures

Exports

Sharing

History

Props

Stagger

PropTypeDefaultDescription
asChildbooleanRender the single child element (such as a Grid) instead of a div, with Stagger's props merged onto it.
arrival"rise" | "assemble""rise"How the items come in: "rise" (the default) or "assemble", which also grows them from 96%, for a hero visual.

It also takes ComponentPropsWithRef<"div">.

Stagger.Item

It also takes ComponentPropsWithRef<"div">.

Accessibility

Items stay in the accessibility tree and in order while hidden. With reduced motion they show at once.

It takes no keyboard input of its own.

Motion

It appears with a `Presence` preset and leaves faster than it came, in about 60% of the time.

With reduced motion: The items show at once, without rising or fading in one after another.

Layout

It renders a div, or its child with asChild, so a Grid or Stack around it lays out the items. Waiting items are only transparent, never offset, so the group has its full size from the start. It has no hit area of its own.

Loop

When to use it

Use it for

  • Product visuals in a hero or a card that quietly show the product working: a chart extending, a row appearing
  • An idle onboarding scene that drifts between states

Not for

  • Anything the user is editing or reading closely
  • Progress or loading states: show the real state
  • One-off arrivals: use Reveal or Stagger

Examples

Activity feed

A bento card's living demo: the total ticks and a row arrives on each step, with a button to pause it for people reading nearby.

Decks presented today

Ticking total

A hero number that ticks to a new value every few seconds, crossfading in place.

Variants and states

A living card

The card steps through three states every four seconds: the total crossfades and a row appears. It pauses off screen and holds still with reduced motion.

Decks exported this week

1,204

Props

Loop

PropTypeDefaultDescription
steps

required

numberHow many steps the demo cycles through.
intervalnumber--bt-loop-cycleTime on each step in milliseconds. Defaults to --bt-loop-cycle (4000); keep it between 3000 and 5000.
pausedbooleanHolds the current step. Pair it with a pause button when a loop runs beside content people read, so they can stop it (WCAG 2.2.2).
children

required

(step: number) => ReactNodeRenders the demo at a step, from 0 to steps - 1.

It also takes Omit<ComponentPropsWithRef<"div">, "children">.

Accessibility

Keep looping content decorative or announce nothing: it changes on its own every few seconds. It holds still under the pointer, with focus inside, while the page is hidden and when paused, and stops on the first step with reduced motion. Beside content people read, give them a pause button that sets paused (WCAG 2.2.2).

It takes no keyboard input of its own.

Motion

It cycles its states every few seconds and pauses while it's offscreen.

With reduced motion: It stays on its first step and doesn't cycle.

Layout

It renders a div as wide as its parent, which can shrink below its content's width, and as tall as the step it shows. It has no hit area of its own.

buttery

When to use it

Use it for

  • Markup or a third-party component that should take buttery's surfaces, spacing and motion
  • A one-off container that needs a surface and a layout but no component of its own

Not for

  • Anything buttery has a component for: use the component
  • Something people press built from a div: use buttery.button, or Pressable

Examples

Pinned first

A list that keeps pinned decks first: each row glides to its new place through layout, with layoutDependency saying when it moved.

0 pinned

Quarterly review
Spring launch
Team offsite
Hiring plan

Speaker notes

Each note is one element given a card surface, padding and a column, rising in as it mounts and lifting on hover.

A link component from elsewhere, wrapped once into a card that presses and lifts, for a list of recent decks.

Variants and states

A surface with a layout

One element takes a surface level, padding and a column with a gap, and rises in as it mounts.

Spring launch

12 slides, Paper theme

Press, focus and hover

A button that scales while pressed, shows the focus ring and lifts on hover.

Props

buttery

PropTypeDefaultDescription
surfaceSurfaceLevelDraws it as a surface level, like Surface.
radius"auto" | RadiusTokenCorner radius for a surface: "auto" nests concentrically inside the surface around it.
padSpaceValuePadding on every side, on the spacing scale.
padXSpaceValueLeft and right padding, on the spacing scale.
padYSpaceValueTop and bottom padding, on the spacing scale.
gapSpaceValueSpace between children, on the spacing scale. Pair it with stack, inline or grid.
stackbooleanLays its children out in a column, like Stack.
inlinebooleanLays its children out in a row, like Inline.
grid1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12Lays its children out in this many equal columns, like Grid.
pressbooleanScales to 0.97 while pressed. Only for things people can press.
focusbooleanShows buttery's focus ring on keyboard focus.
hover"lift" | "tint"Hover feedback: "lift" raises it 2px with a deeper shadow, "tint" washes its background.
enter"fade" | "rise" | "pop" | "stagger"How it arrives when it mounts: "fade", "rise", "pop", or "stagger" for its children in turn.
layoutbooleanAnimates its own position and size changes with spring.smooth, through Motion's layout animation. With reduced motion it simply moves. Switch it with layout={!dragging} rather than adding and removing the prop, so the element keeps its state.
layoutDependencyunknownWith layout: a value that changes whenever its layout does, such as the selected id. Motion measures layout when the element re-renders, and under the React Compiler an element whose own props didn't change doesn't re-render.
truncatebooleanKeeps its text on one line with an ellipsis; string children also become the title.

Accessibility

Adds no roles: the element keeps its own semantics. press only belongs on things people can press, and a development warning says so. Entrances and layout animation follow reduced motion.

It takes no keyboard input of its own.

Motion

It appears with a `Presence` preset and leaves faster than it came, in about 60% of the time.

With reduced motion: An entrance becomes a 100ms fade, or no animation for a stagger, layout changes land at once, a press scales at once, and hover="lift" deepens the shadow without rising.

Layout

The element sizes itself as it would without the factory, adding only padding and gaps from the spacing scale and, with surface, the level's fill, radius and shadow. stack, inline and grid lay its children out as Stack, Inline and Grid do, truncate keeps its text to one line with an ellipsis and string children as its title, and it adds no hit area: the element's own box takes presses.

  • Tokens

    The springs, durations, easings and delays every buttery animation is made of.

  • Choreography

    The larger motion patterns buttery's components share, from the morphing nav menu to a card that becomes its dialog.