Keyframery

Relay Bento

New
  1. Mirapushed to mainnow

    Round totals after tax, not before

    checkout/total.ts

    −total = round(sub) * rate

    +total = round(sub * rate)

    538a478+12−4Queued
  2. Let coupons work on phones

    Live
    4c2a9f0· Kai
  3. Show the delivery date up front

    b71e3d8· Ana
  4. Cache product photos for a day

    0e95c47· Theo

Every push ships

Merge to main and it goes out on its own.

Last build11.8s
  1. Installed 412 packages3.1s
  2. Compiled 86 routes5.6s
  3. Optimized 24 images1.9s
  4. Uploaded the build0.8s
Builtb71e3d810.9s
Built0e95c4712.5s

Follow the build

Each step streams in with its time, so a slow one stands out.

  • TypesNo errors
  • Unit tests248 passed
  • Browser tests36 passed
  • Speed score98

Checked before launch

Types, tests and speed. One failure stops it.

4c2a9f0.northwind.dev
Linen shirt$64.00
Arrives Thursday
TotalSPRING10$128.40

Preview this change

Every deploy gets its own link to try first.

Live in 9 regions

Roll out worldwide

From the first region to the last in under a second.

Requests2,624a minute
30s agoNow

Watch traffic switch

Requests move over as the new version warms up.

It plays by itself: each commit travels from tile to tile through the gaps. Point at a region or a bar for its numbers.

Installation

npx shadcn add @keyframery/relay-bento

Props

PropTypeDefaultWhat it does
RelayBentodiv props + ref—The grid. Lay the tiles out with ordinary grid classes (grid-cols-*, grid-rows-*, col-span-*): the light finds its way through whatever gaps there are, at any size, and reroutes when the layout changes.
ref{ send }—send from outside the bento, if the code that runs the story lives there.
RelayTile idstring—What send calls the tile.
RelayTile titleReactNode—A short name, set under the tile's picture. It also names the tile for screen readers.
RelayTile descriptionReactNode—A line or two under the title.
RelayTile sidebooleanfalseSets the title and description beside the picture, on the left, whenever the tile is wide (36rem or more). Good for a wide, short tile like a map.
--relay-tileCSS color—The tiles' fill. By default it's mixed from your theme's muted and background colours; set it on the bento, or anything around it, to choose your own.
useRelay().send(from, to, options?) => Promise<void>—Runs light from one tile to another, or to several at once, and resolves when it lands. from and to are tile ids, elements inside tiles or refs to them: the light leaves beside the thing that sent it and lands beside the thing that receives it.
options.tone"accent" | "success" | "danger""accent"The light's colour, from --kf-accent, --kf-success or --kf-danger in your theme. Unset, they're blue, green and red.
options.dockbooleanfalseThe light carries on past the tile's edge into the element it was sent to, and settles there as a dot that rings once. Good for a status dot or an avatar near an edge.
options.signalAbortSignal—Stops the light where it is. It fades out, and send resolves at once.
options.pacenumber1How fast the light travels: 2 is twice as fast.
useRelayLoop(story: (signal) => Promise<void>, { rest? }) => void—Plays a story while the bento is on screen: it runs, rests for rest ms (1600 by default), and runs again. Scrolling away or hiding the tab aborts signal, and the story starts over on return.

Accessibility and motion

  • The light only ever travels through the gaps, so it never covers a word of your content, and it finds its route from where the tiles actually are. Any grid layout works, down to one column on a phone.
  • It's drawn on one canvas laid over the grid that ignores the pointer, so every button, link and selection inside a tile works as usual. Nothing is drawn while no light is moving, and nothing plays while the bento is off screen.
  • Each tile is a group named by its title. The light is hidden from screen readers, so say what happened in the tile itself, as the demo does with its statuses.
  • With reduced motion on, nothing travels: send resolves at once, the receiving tile's edge glows for a moment, and a loop plays its story once and stays where it ends.