Skip to main content
7 min read

Layout & the scroll sequence

Every panel is 32 px tall. Width picks the design problem — and the space rules are opposite per device.

Where content is allowed to live

SizeUsed onSpace rule
64x32All end products (the V2, and every SCROLL at its narrowest)Maximize the space. With this few pixels, minimize no-content areas — every pixel should be earning its place.
>64x32 (128 / 192 / 384)SCROLLRespect a safe zone. Keep content inside roughly x 10–181 on a 192-wide app, with 6–10 px of padding at the left and right edges.
A 192-wide panel with the outer 10 pixels on each side shaded red and the safe zone outlined in green
192 wide: the shaded edges are where the apps before and after yours are still on the glass.

The #1 scroll rule: design for the sequence

On SCROLL the display is a stream moving right to left. An unknown app plays before yours, another after, and your own pages rotate in between based on the end user's inputs. Scroll hardware comes in threes — Studio 192x32, Pro 384x32, Premier 640x32 — so on the wider panels your app is on the glass at the same time as one or two neighbors.

An app with content at pixel 0 or 191 visually merges with whatever plays around it. Design the app to read as its own unit — edge padding, a contained or boxed trailing zone, or a closed frame — and always preview it in sequence, not alone. Studio's Preview your app in action card does exactly that: your app scrolling between two stock neighbors on every panel size, with a Mark app seams toggle.

The padding is per app, not per page. Pages inside one app can sit closer together, but the app's outer edges need the buffer.

Scroll apps have two placement options: edge padding or centering. Not everything should be centered, but lean on the center of the canvas rather than hard-justifying content against the left and right sides. Reduce wasted space within the safe zone too — a scroll app that parks a small cluster of content in one corner of 192x32 is wasting the panel.

These bind the AI's defaults, not you

Your own design may run elements to the edge deliberately — some shipped scroll apps draw a thin 2 px accent stripe at the far-left edge as a frame. When an AI is laying out a scroll app on its own, it keeps everything — text, data, pixel art — inside the safe zone.

The one adaptive branch

if c.width >= 128:
# wide layout
else:
# 64-wide layout

The catalog treats 64 as "narrow" and everything else as "wide"; the layout linter understands exactly this shape. Write different copy for each width, never clipped copy ("MARKET HOLIDAY""HOLIDAY"), and drop elements entirely on 64 rather than squeezing them. 55 of 59 classic/scroll pairs in the catalog ship a byte-identical app.star — write the width branch once and both builds come free.

Every app must identify itself

It is not obvious what an app is ~85% of the time just by seeing its data. Because apps rotate in a shared stream, add a splash / intro page to create a break from the preceding app — or, if a splash page isn't warranted, make sure the app carries a pixel-art identity or a title so a viewer can context-switch into its data feed:

  • A clock doesn't need a title or splash — it's a clock, we get it.
  • Snowfall likely needs a title, but with pixel art of snow falling a splash page isn't required.
  • The Minecraft app opens on the Minecraft logo.

Location-based apps always show the location on the image. If the input is a zip code and there's no location API, display the zip itself.

Classic panels (64 / 128): the bands

Vertical bands, measured from the catalog:

A 64x32 panel divided into a filled header strip, a large hero value, and a small second row with a footnote on the right
Header y 0–7 · hero y 8–13 (top of the big face) · second row y 17–22 · footnote y 23–27.
  • Header y 0–7 — a filled strip with centered 5x7 / 4x5 text, or a 4x5 eyebrow in dim gray.
  • Hero y 8–1316x20 on wide, 10x16 on narrow.
  • Second row y 17–22, footnote y 23–27.
  • Margins 2–6 px. A left icon sits at x = 1, vertically centered; text next to a 24 px icon starts at x 28. One 1 px hairline under the header is the whole grid.

Lists: divide the height evenly (lh = c.height // n, 3 rows on 64, 4 on wide). Row anatomy is left-label / right-value, and the right side is measured first — draw the value, compute what's left, then fit / clip the label into it. Nothing in the API clips; this ordering is what prevents collisions.

SCROLL: the house kit

The modern scroll apps share a page grammar (github-pulse-scroll lines 1–237 in the catalog is the canonical copy):

  • an accent rail wearing the app's state color,
  • a chip row — a state-carrying tab() chip, meta right-aligned,
  • a content band (y 8–31) split into named zones by vlines.

"An amber REVIEW chip above a green ALL CAUGHT UP is the page contradicting itself" — compute state first, then dress the chrome in it. List bullets are a 2x2 colored square; reflow columns by item count (2 columns while ≤ 6 items, 3 after).

Spacing rules (hard requirements)

  • Nothing draws off the canvas. The renderer clips silently at the borders — a draw one pixel past the edge just loses that column, with no error. Any element near an edge is placed by its measured width (or right-aligned against the edge), never at a hand-picked x, and the worst-case string is rendered and checked at both edges.
  • 1 px minimum buffer between text and the next asset — line, polygon, pixel art, anything.
  • 1 px minimum between vertically stacked text rows. If two rows can't keep the gap, reduce the font size until they fit — never let rows touch.
  • Text on a filled shape is centered both ways. Black text on a pill: the pill is centered vertically and horizontally on the text, high contrast, even padding all around.

More content than fits: three mechanisms, none of them animation

  1. Pagespages: [today, tasks], one function per name; the panel rotates them. 1–3 is typical; the 8-http.get budget per render is the ceiling. See Pages.
  2. Frames — time-multiplex inside one page: frame = (ctx.now.unix // 60) % 3 with refresh: 60, plus a hidden _debugframe input for previewing.
  3. Truncate with an explicit overflow count — show 3, then "+N MORE" right-aligned in 4x5.

Next