Gesso
Getting started

Changelog

What changed in Gesso, newest first, grouped by month.

Every merged change that someone installing or reading the kit would notice gets an entry here, in the same PR as the change. Gesso ships from main through the registry, so an entry is dated by its merge and links its PR rather than a version number. The months are in the table of contents on the right.

Each entry is marked New (an item, token, or page that did not exist), Changed (an existing item looks, reads, or behaves differently), Fixed, or Docs. Breaking goes first when an install needs edits to keep working.

October 2026

Segmented well corners

Changed · Oct 19 · #43

The Toggle group well follows the look's control corner, 4px rounder than its items, so Dry, Washi, and Ramson now draw it with 12px corners instead of a pill, ICS keeps the pill, and Vistula draws it at 6px. Chips still pin 8px in every look.

Vistula

New · Oct 18 · #43

Vistula is a prefab for the Concrete & Moss direction. Pine is the primary, vermillion is the support, and danger is a crimson so the accent does not read as a delete. It sits on gray paper, Archivo sets UI and titles, Newsreader sets body, and the corners are 2px. Install it with shadcn add @gesso/vistula and set data-theme="vistula".

Studio starts from Vistula the same way it starts from Dry, Washi, and ICS. Its copy now follows the swatches and paper you pick rather than the preset, so a tweaked Vistula keeps its dark block, its crimson danger, and the shadcn add line it needs. The docs color picker and the home page look switcher both include it.

Image field as a tile

Changed · Oct 9 · #42

Image field takes variant="tile", a square button showing the image, like a profile's photo, that opens the same choices in a popover. A file can still be dropped or pasted on it.

Variable search matches word by word

Changed · Oct 8

Block editor's variable lists find a variable when every word typed starts a word of its name, group, or key, so "unit 1 make" finds Unit 1 · Make. A number matches whole, so 1 doesn't find Unit 10.

Images say when they'll look soft, and fill their own alt text

Changed · Oct 8

Image field says what makes a good file for each shape, and warns when a picked logo, photo, or card image is too small to look sharp. In Block editor, picking an image while its alt is empty fills the alt from the image's name, and alt text that reads like a file name gets a warning.

Image field

New · Oct 8

Image field is the image picker from Block editor as an item of its own, so a settings page can pick a logo or a photo the same way a letter does. It takes ImageLibraryProvider for uploads and recent images, and fields for an image that fills in when it sends. Past the nine newest images, a search finds any of them by name and Show all lists every one. In Email blocks, PictureField is now BlockImageField, and BlockImageLibrary and its provider are Image field's under their old names.

Pictures drop, paste, and show in their block's shape

Changed · Oct 8

Block editor's picture field takes a file dropped on it or pasted, beside Choose a file. It shows a picture the way its block does, with a shape of wide, logo, card, or round, so a logo sits at letterhead height on a checkerboard and isn't cropped. The recent pictures carry their names and a check on the one in use, and Replace opens in a popover.

Changed · Oct 8

Block editor's email theme takes a logoUrl, and a header without a logo of its own shows it, on the canvas and in the compiled letter. It isn't saved with the letter, so changing it reaches every letter that leaves its header's logo empty.

Time picker

New · Oct 8

Time picker is a time field that matches Date picker: a button that opens the day's times a step apart, with keys and typing to jump, and an "HH:mm" value like a time input's.

Email blocks say image, not picture or photo

Changed · Oct 8

Block editor's card and signature settings, their checks, and the image row's hints call every image an image, so a card's Picture group and a signature's Photo group are both Image now.

Vertical tabs can group a run of tabs under a label

New · Oct 8

Tabs has TabsGroup, which puts a labeled, indented run of triggers inside a vertical list, like the parts of a settings area that belong together.

Variable lists read by name, with an icon per group

Changed · Oct 8

Block editor's lists and fields panel show a variable's name and nothing technical, so a row without a note no longer falls back to its key, and a chip shows its label on hover instead of its token. A variable's new shortLabel reads under its group's heading, like Name under Rep, and variableGroupIcons puts an icon beside each heading.

A new variable can stay plain text

Changed · Oct 8

Block editor's onCreateVariable can resolve with a string, which writes that text in place of the {{ and the name, so a host can let a person keep a one-off as words.

A long group in the fields panel folds to one line

Changed · Oct 8

Block editor's panel of what a document fills in folds a group of more than six to its name and count, like a unit card's details, and pointing at the fold rings that whole group on the canvas.

A highlight can ring a whole group of variables

Changed · Oct 8

Block editor's highlightedVariable takes a path too, like units.1, and rings every variable under it, so a host can point at a whole unit card at once.

A description row's parts line up on one line

Changed · Oct 8

Description list centers a row's label, value, and action on each other once labels take their own column, so a chip or an avatar in one no longer leaves the others sitting higher.

The block editor says your word for a variable

Changed · Oct 8

Block editor takes variableNoun, so its insert buttons, its {{ list, and the panel of what a letter fills in can say "detail" or whatever your product calls them. That panel's heading now reads Variables where it said Fields, and the create option reads New variable.

A section's actions stay beside its title

Changed · Oct 8

Section keeps its actions on the title's row and runs the description under both, where a long description used to push the actions under it.

A stat can set at a medium size

Changed · Oct 8

Block editor's stat takes size, Large or Medium, so a longer number like a price range fits on one line on a phone.

Creating a variable can ask for details first

Changed · Oct 8

Block editor's onCreateVariable can resolve with null when the person cancels, and a chip made after a host's own dialog still lands where {{ was typed.

A description row's action sits at its top right

Changed · Oct 7

Description list puts a row's action at the top right of the row, level with the label when labels stack above their values, where it used to sit beside the value below the label.

The issues chip sits beside undo

Changed · Oct 7

Block editor moves the issues chip to the left of the toolbar, after undo and redo in Edit and after Copy in Preview, so the right side holds just the viewport and mode.

The canvas spaces blocks like the email

Fixed · Oct 7

Block editor no longer adds its own padding around blocks, inside sections, or around the letter, so a section's padding and gap on the canvas match what sends. Blocks keep a little room for hover and selection that doesn't take up space, and the block rail sits far enough out to clear a full-width block's selection.

A copy that reads like what it sends

New · Oct 7

Block editor outlines every block a copy can change. Variables take value, which the canvas shows in place of the chip and as the picture for a picture slot. inspectorContainer puts the settings panel anywhere on the page, BlockInspector takes backLabel for a way back to the host's form and children for the host's own fields, and highlightedVariable rings a variable from that form.

A note beside each variable

New · Oct 7

Block editor variables take detail, a short note the picker shows in place of the key, like "1,240 contacts" or which templates use the field.

Templates and copies in the block editor

New · Oct 7

Block editor fills in a copy of a template with fill. Only open blocks change, none are added, moved, or removed, and the issues chip counts only what the copy can fix. fillControls puts a Fixed or Editable choice beside each block's name in its settings for which ones a copy can change, and email blocks open pictures, cards, buttons, stats, and pull quotes by default and keep the footer fixed.

Create a variable while writing

New · Oct 7

Block editor takes onCreateVariable, so the {{ list and the braces picker end with Create and what was typed when no variable has that name. The host saves it and resolves with the new variable, which goes in as a chip.

Small empty state

New · Oct 7

Empty state takes size="sm" for a card's image slot, a side panel, or a popover: a 40px well, a body-size title, and a caption. The block editor's inspector uses it when nothing is selected, and the email blocks' picture placeholder uses the same sizes.

Color picker

New · Oct 7

Color picker is a color field that opens a square for saturation and brightness, a hue slider, a hex field, and any swatches you pass, in place of the browser's own color window. Where the browser can sample the screen, an eyedropper sits beside the hex, and renderTrigger swaps the button for one of your own. Email blocks use it for a custom section background or divider color.

Brand moments in email blocks

New · Oct 7

Email blocks carry a brand past its logo in the letter's accent.

  • Stat is a new block: one large number with a short line under it.
  • Pull quote is a new block: someone else's words under a large mark, then who said them.
  • The divider takes a style: a line, an ornament that breaks the rule with a rounded diamond, or a row of dots. A divider's color paints its rule, its diamond, or its dots, and one saved before keeps its line.
  • EmailBrand takes an optional accentColor for these, and the button color stands in without one.
  • A section's background and a divider's color are swatches from the letter's brand, and the last swatch opens the color picker. A new divider follows the brand's border color, and one saved before keeps its own.
  • Padding, gap, and spacer presets no longer repeat their pixel values under the buttons, and custom padding is two number fields, top and bottom and sides, instead of a CSS string.

Pictures in block settings

New · Oct 7

Block editor shows a block's picture as a thumbnail and its name, or a chip for a variable, instead of a link.

  • imageLibrary gives the picture an upload and recent pictures from your app. With no picture, the panel offers those and Use a link or field, one field for a pasted link or a variable, and the link only shows when someone picks it.
  • On the canvas, a picture that is a variable draws a placeholder named for it, instead of a broken image and a request for {{field}}. An empty image block asks for a picture in the letter's theme.
  • PictureField and PicturePlaceholder give a custom block the same card and placeholder.

Variables as chips in block fields

Changed · Oct 7

Block editor fields read the way the letter does, without {{key}} showing anywhere a person picked a variable.

  • A block's fields in the inspector, like a card title, a button URL, or an image source, show variables as chips. Typing {{ opens the list, which keeps filtering across a space so a name like Rep email narrows it, and the braces button shows only while the field is in use, instead of beside every field. The saved value is still a string with {{key}} in it, so documents and the compile do not change.
  • The canvas draws a header, card, signature, button label, spec row, and footer line with chips. A chip on the canvas takes the color of the text around it, so it reads on a dark button and on a light page in dark mode, which it did not before.
  • With nothing selected, the inspector lists the fields the document uses, grouped like the list, with how many blocks use each. Pointing at one rings its chips on the canvas, and clicking scrolls to the next.
  • VariableInput and VariableText give a custom block the same field and the same canvas chips, and documentVariables lists a document's fields.

Mentions, page blocks, and other app extensions

Breaking · Changed · Oct 6 · #39

Extensions the Ramson apps made to a dozen items are now part of the kit.

  • Markdown editor no longer pads its own box. The block handle hangs 52px left of its block and the card rail 44px right of the editor, so give the container that room, as the old pl-4 sm:pl-14 pr-4 (and sm:pr-16 with cards) did, or mark a box around it data-markdown-gutter-edge and the handle hangs just outside it.
  • Markdown editor takes mentions, so @ opens a picker that writes [@Name](mention:<id>) as a person chip, and MentionChip draws one in read-only markdown. A line holding only [[<id>]] is a page block that pageBlock draws as your card and that drags by the card. taskBox draws the box for a task your app owns, a field takes content and appearance: 'inline', and LinkChip draws a link chip in a node view.
  • In the editor a dropped block leaves a caret, a field typed or pasted as text becomes its chip, a note that opens with a --- rule keeps it, chips that open something show the pointer, and hashTagColor leaves out yellow, so tags keep the colors they had.
  • Block editor email blocks take an EmailThemeProvider, so the canvas paints in the letter's theme, such as a tenant brand. A new section has no background and follows the letter's color. A send path can compile letters on the server or in a test from email-blocks/serialize, and the rich-text and variable helpers are in block-editor/rich-text-model and block-editor/variables-model, still exported from the editor entry.
  • Work mark has a cancelled status, a muted disc with a cross, and shape="square" for a task box. A Record<WorkStatus, …> of your own needs a cancelled entry.
  • Health face takes ink, for a state the three faces don't name.
  • Record list rows take openable={false} for a row that only reads, trailing for meta the display menu does not govern, and a node as title. A click on the empty space among the right-hand slots opens the row, a click in the status icon's menu does not, and the id column grows to fit a long id.
  • Date picker takes variant="ghost", and renderTrigger for a trigger of your own, such as a Chip trigger.
  • Scroll area takes orientation, for a sideways strip, and viewportRef for the element that scrolls.
  • Filter chip takes size="sm" to sit in a tab row.
  • Sidebar has SidebarTrayHeader, a phone header that in the well sits on the tray above the card and stays put while the card tucks away.
  • In App shell the stage eases as it slides on a phone, in push and well, where it jumped before. Command's disabled rows and input dim to half opacity.

Email card, image row fit, brand snapshot

Changed · Oct 6 · #38

Block editor email blocks gain a card, and the image row, brand, and canvas behaviors that Loop built on the pack.

  • The card is a picture beside an eyebrow, a title, a description, a meta line, and a secondary button, the horizontal frame of Interactive card. On a phone the picture stacks over the copy at 16:10. The sample letter includes one.
  • An image row takes a fit. Match, the default, gives each column its photo's shape so the images end flush, Crop cuts every image to one height, and Free shares the row by width. A host can pass imageSrc to serve real crops where Outlook ignores the CSS.
  • A letter can keep its brand on meta.brand, so a later settings change leaves it alone.
  • The letter rounds at the brand radius plus 4px, in Edit, Preview, and the sent HTML, and the card follows the same radius.
  • canvasStyle and frameStyle paint the gutter and the letter, and the footer sits below the letter card in Edit and Preview, where it prints.
  • Preview takes the letter's full height instead of scrolling it inside a 512px frame.
  • Icon button takes tone, as Button does, and a toned icon draws in the tone's own color. The block editor's delete buttons, on the toolbar, the rail, and each spec table row, use danger.
  • The issue count is a Chip trigger, ringed in the worst issue's tone, with the count in the ink rather than a darker red.
  • Inspector panels group related inputs under a name, like a card's Picture, Copy, and Button, with a rule between groups. Every input is now named by its label, which most were not, and the panel sits on the raised surface so the inputs read as fields.

New · Oct 6

Gallery arranges a list of children as full-width rows, an even grid, or a masonry. A grid stretches a short card to its row, and masonry lets each card keep its height. Interactive card stacks its text under the media when a horizontal card is narrower than 20rem. A grid can stretch the row, and the picture keeps its size either way.

Changed · Oct 6 · #36

Block editor email blocks gain a letterhead header, a spec table, a signature, and a footer. The header sits in a section with the rest of the letter: a logo or name, a detail line, and a strong-border rule under that row. The footer carries Unsubscribe, and an optional Email preferences link. A variable can take a currency or number format, so 23480 prints as $23,480 and 1240 prints as 1,240.

Interactive card

New · Oct 6

Interactive card is a card of slots, stacked or side by side, that can be the link. It lifts, zooms its media, or reveals its actions on hover and focus, and the travel stops under reduced motion. The picture across the top is 16:10, square, or 3:4. An overlay on the picture takes a scrim, a line at the bottom, and a control in the top right, such as saving the note. inset pulls the picture in by 8px and rounds it with the card.

Info can take the brand

Added · Oct 5

Set data-info="primary" or data-info="support" on the root to draw the info tone, and every info Alert, toast, and Badge, in a brand hue instead of the categorical blue. Retune shows how, and Studio has an Info control to try it.

Surfaces, rebuilt for dark mode

Changed · Oct 5

The page, a card, and anything that floats now share bg-surface-raised, so a card is a section of the page and a menu or dialog separates by its border and shadow. Fields sit a step down on bg-surface, and the tray behind the sidebar and wells sit a step below that, in light and dark. Dark mode is darker and runs on three new half steps of the neutral ramp, --neutral-750, --neutral-850, and --neutral-925: the page is 925, and fields, the tray, and wells are 900, so the sidebar reads lighter than the page. Card's default variant and App shell's stage use the raised surface.

Two borders, by job

Breaking · Changed · Oct 5

Borders are washes of the contrast ink on the alpha scale, so a line reads the same on any surface. border-border is 4% and outlines what is static on the page, such as a card, an accordion, or the sidebar edge. border-strong is 8% in light and 12% in dark, and draws overlays, fields and controls, table and list rows, and every separator, including the ones in menus. border-subtle is removed, so use border-border in its place. Table's footer, which had no border color, now draws the strong rule, and a selected Table row draws in bg-selected, since the raised surface now matches the page. Empty state's bordered variant and the email builder's empty image slots keep a dashed strong border.

Shadows that show in dark

Changed · Oct 5

In dark mode the shadow ink drops to black and each layer triples its weight, to 24, 32, and 48%, since the light ink was as dark as a dark page. Light shadows are unchanged, and overlay scrims are a little deeper in dark.

Selected rows and toggles

Changed · Oct 5

A selected Sidebar row and a selected Toggle group item set their label in text-fg and their icon in text-primary, so the brand color sits in the icon and the fill. The selected toggle item also gets a stroke in the default border. The tone fill is 24% in dark, so a tinted chip or button stands off the darker page.

Panels sit on the well

Changed · Oct 5

Description list rows, Stat strip tiles, and the markdown editor's plain callout use the sunken well (bg-surface-sunken) and not the fill wash, so a resting panel no longer reads like a hovered row, and it is a little lighter in both modes. bg-fill is for interaction again.

A lighter placeholder

Changed · Oct 5

Input and Textarea set their placeholder in text-fg-subtle, so an empty field with an example no longer reads as filled in. Command and Command palette keep text-fg-muted, since their field has no visible label and the placeholder is its instruction.

Badge and Chip take a category color

Added · Oct 4

Badge and Chip take a color from the category palette, as in <Chip color="orange">High</Chip>, so a label that keeps its hue no longer passes style={catChipStyle(...)}. CategoryTag is Badge with a color now, and variant="tag" with a style still works.

Warning text stays in ink

Fixed · Oct 4

The email blocks' warning note and the block editor's over-budget size read in text-fg with a yellow warning icon, since yellow text is not readable on white.

A look can warm the category hues

Added · Changed · Oct 4

A look may retune --cat-red, --cat-orange, and --cat-yellow, and danger, warning, and every chip and segment in those hues follow. Washi now has a brick red and a warmer orange and yellow, and its danger label goes from 3.7:1 to 4.6:1. --destructive resolves per look now, so a look's red reaches it. Retune shows how.

Use a tone

Added · Oct 2

Color has a "Use a tone" section that says when to pick each tone, shows data-tone on your own markup, and lists which components take which tones.

A lighter track

Changed · Oct 2

--sys-track, the unfilled edge of a Switch rail, Checkbox, and Radio, is 32% of the contrast ink now, one step down from 48%. It reads about 2.0 to 2.7:1 over the surfaces, which is under the 3:1 that WCAG 1.4.11 asks for on a control edge.

The warning toast takes the tint

Changed · Oct 2

A toast.warning takes the warning tint as its fill now, like the other status toasts and the Alert, and no longer stays on the plain surface.

A lighter info solid

Changed · Oct 2

The info solid is the cat blue mixed 24% toward neutral 900, down from 32%, so it reads brighter. A white label is 5.2:1 on it. Success stays at 32%, since a lighter green does not hold a white label.

A yellow category, and a yellower warning

Added · Changed · Oct 2

The category palette has a ninth hue, yellow, as --cat-yellow in the primitives and --sys-cat-yellow in the system, and it is a value for CategoryColor and for a Segmented bar color. The warning tone takes it as its base in place of --cat-amber, so a warning is brighter and yellower, and keeps its dark label, and its tint is 16% in light as well as dark so the wash stays yellow on white. Amber is unchanged for chips, segments, and PriorityBars.

Deeper success and info solids

Changed · Oct 2

The success and info solids are the cat green and blue mixed 32% toward neutral 900, so a white label works on them. Success is 4.9:1 and info is 6.1:1. A tone has a --tone-base now, the hue itself, and the tint, fill, edge, and ink derive from it, so the alerts and quiet fills stay clean while only the solid is deeper. The cat colors, and so the chips and segments, are unchanged. Warning keeps its dark label, since a deeper amber turns brown.

A stronger tone fill

Changed · Oct 2

The tone fill is 16% of the solid now, and its hover is 32%, one step up each on the alpha scale. Chips, a secondary or ghost Button in a tone, and an alert's action read a little firmer, and small ink on the fill stays above 5:1 in every look. The neutral tone keeps the kit's own fill.

Chips read tone, and the subtle tints are gone

Breaking · Changed · Oct 2

Chip, Chip trigger, and Filter chip set a tone and read it, so a default chip is the primary tone's fill and an outline chip is a primary-tone ring. A chip sets its own tone, and data-tone="support" on it flips a chip to the second hue. --brand-primary-subtle, --brand-secondary-subtle, --sys-soft, and the bg-primary-subtle, border-primary-subtle, and bg-secondary-subtle utilities are removed, so a look sets only its two hues, their foregrounds, its faces, and its radius. Use data-tone="primary" with bg-tone-fill or border-tone/(--alpha-32) where you used a subtle utility. Chip fills and rings are now a mix of the brand color and shift a little from the hand-picked tints, most in Ramson light. The ICS ramps keep only the five stops the look reads, and six Washi and Ramson tint colors that nothing read are removed.

One red

Breaking · Changed · Oct 2

The red ramp is gone, and danger pulls from --cat-red through --destructive, like success, warning, and info pull from their cat colors. The Delete button, the danger alert, and a red chip now share one red, a little lighter and pinker than the old red 600. If your own CSS points at a red stop, write the oklch() value instead.

The second hue is the support tone

Breaking · Changed · Oct 2

bg-support and text-support-fg are gone. The second brand hue is the support tone now, so put data-tone="support" on the element and use bg-tone and text-tone-fg, and its tint, ink, fill, and edge come with it. Button, Badge, Progress, Switch, Checkbox, and Radio take tone="support", and the tenant picker's support mark and the sparkline's gradient read the same tone. --brand-secondary and its subtle tint are unchanged.

Brand ink is the primary tone

Breaking · Changed · Oct 2

--sys-primary-ink and text-primary-ink are gone, since the system has no primary of its own. Put data-tone="primary" on the element and use text-tone-ink, which is the same brand mixed 48% toward the page ink. The Markdown editor sets that tone on its root, so its links, wikilinks, and syntax keywords look the same.

Status without its own tokens

Breaking · Changed · Oct 2

--sys-danger, --sys-success, --sys-warning, --sys-info, and their -fg pairs are gone. Danger is --destructive and --destructive-foreground now, which is what shadcn blocks already read, so a look that set --sys-danger sets --destructive instead. Success, warning, and info are set only in their [data-tone] blocks, so to retune one, write the --tone-solid and --tone-fg pair on that block, as the retune guide shows. HealthFace, WorkMark, and PriorityBars draw in the categorical green and amber and in --destructive, and the Markdown editor's syntax colors now mix at --alpha-48, which lifts amber code in light mode from 4.47:1 to 6:1.

One way to say a status

Breaking · Changed · Oct 2

A status is a tone now, so the kit-own status utilities and tints are gone. bg-success, bg-warning, bg-info, their -fg and -muted siblings, and bg-destructive-muted no longer exist, and neither do --sys-danger-muted, --sys-success-muted, --sys-warning-muted, and --sys-info-muted. Put data-tone="success" on the element and use bg-tone, text-tone, text-tone-fg, or bg-tone-tint, which draws the same fill and tint. --sys-danger, --sys-success, --sys-warning, --sys-info and their -fg pairs stay as the source each tone reads, and bg-destructive and text-destructive-foreground stay as the shadcn fallback. A typed toast takes its tone from sonner's data-type.

Tone

New · Oct 2

A tone is a hue a component can wear, set with data-tone, and it remaps one set of --tone-* tokens that bg-tone, text-tone-fg, bg-tone-tint, text-tone-ink, bg-tone-fill, and border-tone-edge read. Button, Badge, and Alert read it. A Button takes a tone of neutral, primary, support, danger, or inherit, since success, warning, and info are feedback hues, and a Badge takes any tone. variant="destructive" still works and means the danger tone. Color has the tokens. Alert's alertButtonScope is gone, since an alert action takes tone="inherit", and alertVariants and alertIconVariants no longer take a variant. Progress, Switch, Checkbox, and Radio group item take a tone too, defaulting to primary, so a finished upload can be a success bar. Segmented bar's status segments and the destructive item in Menu and Context menu read the tone as well.

Status from the categorical hues

Breaking · Changed · Oct 2

The green, blue, and amber ramps are gone from primitives.css, so --green-*, --blue-*, and --amber-* no longer exist. Success, warning, and info now use --cat-green, --cat-amber, and --cat-blue, which are unchanged, so their solids are lighter than the old green and blue 600 and the warning is a little yellower than the old amber 500. Each *-muted tint is now its solid mixed into the sunken well, at 8% in light and 16% in dark, so a look that overrides danger tints it. If your retune points at one of the removed ramps, write the oklch() value instead, as the retune guide shows now.

Alpha scale in the primitives

Breaking · Changed · Oct 2

The alpha scale moved from system.css to primitives.css and is --alpha-* now, where it was --sys-alpha-*. It has ten steps, 4, 8, 12, 16, 24, 32, 48, 64, 82, and 100, and every wash in the kit picks one, so the hard-set percentages are gone. If you write bg-x/(--sys-alpha-8) or opacity-(--sys-alpha-64) yourself, drop the sys-. A few values moved a step to land on the scale: the hover wash is 16% in light and 24% in dark, dark fill is 12%, the track is 48%, the brand ink and chip ink mixes are 48%, the chip fill is 32%, and the shadow layers are 12% and 16%.

Fixed · Oct 2

  • Sidebar's desktop rail is a <nav> named "Sidebar", isActive sets aria-current="page" through asChild too, SidebarTrigger reports aria-expanded, and the menu and group actions reach 44px on a touch screen at any width.
  • App shell takes skipLink, a link that jumps past the rail to the stage.
  • Tenant picker's index marks the current workspace with aria-current, and in seals the arrow keys move a pending choice rather than switching, so Enter, Space, or a click switches and Escape leaves it.
  • Breadcrumb's ellipsis is a named button with onClick or your own control with asChild, and links grow to a 44px hit area on touch.
  • Tab switcher with no active tab shows every tab off instead of tracking a pick of its own.
  • Page header's stacked Back button shows backLabel, "Back" by default, so its name matches what it says.
  • Alert's info and success variants are role="status" rather than assertive alerts, the dismiss button takes dismissLabel and is 44px on a touch screen, and dismissing hands focus to the next control instead of the page.
  • Toaster wraps a long message inside the toast, and its action and cancel buttons are 44px tall on a touch screen.
  • Progress fills by max, and an indeterminate bar, which drew nothing, sweeps a segment that reduced motion holds still as a tint.
  • Empty state hides its icon well from assistive tech and takes as for the title's level.
  • Skeleton takes a label that announces loading as a polite status.
  • Resizable's handle is named "Resize" by default, its grip icon is hidden, and its line stays visible in forced colors.
  • Scroll area takes viewportProps, so the viewport can take focus and a name, and its thumb stays visible in forced colors, as do Separator's lines.
  • Accordion's caret is hidden from assistive tech, and Collapsible's trigger shows the kit's focus ring.
  • Item's ItemGroup is a list and its rows are list items, with a link or button row kept in a list item of its own.

Data display fixes

Fixed · Oct 2

  • Record list gives each group's rows their own named listbox, so headings, the create button, and empty states sit outside it. The keys act only when a listbox has focus, so Enter on a heading collapses it and j typed in a field stays in the field, and they walk rows in screen order across groups. The display menu's rows are checkboxes that Enter toggles.
  • Cell fields: Enter on a url cell's link follows it, every editor and checkbox is named by its column, and the select editor is a listbox that takes focus, moves with the arrow keys, and closes on Tab. The date editor waits for Enter or a blur like the others, so Escape no longer keeps a date. In forced colors only the focused cell shows an outline. PopoverListbox is new, for your own choice editors.
  • Table's heads default to scope="col", a change of sort is announced, and a table wider than its container becomes a named region a keyboard user can focus and scroll, with scrollLabel to name it.
  • Sparkline keeps its line inside the box rather than clipping half of it at the edges, a gradient line's fill stays primary, and it takes SVG props, becoming a named image with a title or an aria-label.
  • Health face, Priority bars, and Work mark take a label, for a translated name or "" to hide a mark that text beside it already names. Urgent priority stands its three bars at full height, so it differs from high by shape and not only by ink. Check mark forwards its ref and SVG props.
  • Segmented bar draws its segments in CanvasText inside a bordered track in forced colors, where they had vanished, and its legend rows take focus and highlight their segment the way pointing does.
  • Stat strip keeps its dividers in forced colors.
  • Avatar's image crops to the circle instead of stretching.
  • Badge changes on hover only when asChild makes it a link or a button, and then shows a focus ring.
  • Card's CardTitle takes as to set its heading level.

Overlays fixes

Fixed · Oct 2

  • Dialog keeps 16px from the edges of a narrow screen and scrolls content taller than the screen inside it. Its close button is 36px, and 44px on a touch screen at any width rather than below a breakpoint, and a long title wraps before it.
  • Alert dialog gets the same 16px margin and the same scrolling for tall content.
  • Sheet's header keeps a long title clear of the close button, and the button is 44px on a touch screen at any width.
  • A Tooltip inside the kit's TooltipProvider uses that provider, so the provider's delayDuration applies and a row of tooltips opens one after another without waiting again. A tooltip with no provider around it still brings its own.
  • Hover card warns in development when a trigger has neither href nor asChild, since that trigger renders an <a> the keyboard cannot reach.
  • Icon button takes a TooltipProvider's delay unless tooltipDelay is set, so it joins a row of grouped tooltips.

Forms fixes

Fixed · Oct 1

  • Field's FieldError with several errors renders its list in a <div>, so it no longer breaks HTML or hydration, and a message that repeats shows once. orientation="responsive" now goes beside the control once its FieldGroup is 32rem wide, with no container class to add.
  • Input and Textarea set their text at 16px on every screen, so iOS Safari no longer zooms in when one takes focus. @gesso/input now installs the tokens it uses, like its siblings.
  • Input group's addons and padding follow the writing direction, so a start addon sits on the right on a right-to-left page. A plain pe-14 on the input now widens the room for a longer addon like .tech, where before it lost to the group's padding and the text ran under the addon.
  • Checkbox's checked="indeterminate" fills the box and draws a dash, where it drew a check on an empty box. In forced colors a disabled box that is checked shows its check in Canvas on the GrayText fill, where it had vanished.
  • In forced colors a disabled Radio group option that is chosen keeps its mark in GrayText, where it looked unchosen.
  • Switch shows a border-destructive track for aria-invalid, like the other controls. In forced colors an off thumb is ButtonText rather than the track's own color, and a disabled switch that is on fills with GrayText under a Canvas thumb, so every state shows which way it is set.
  • Combobox honors defaultOpen, and a chosen row is described as "Selected" rather than carrying it in its text, so a row without a value keeps the same value when it is picked and no longer matches a search for "selected".
  • Calendar's month menu uses the calendar's locale rather than the browser's language, and a day's data-day is an ISO date, so neither differs between the server and the browser. With dir="rtl" and navLayout="around" the month arrows no longer turn twice and point the wrong way.
  • Chip's dismiss button draws its focus outline in the contrast ink, where the class named a color that does not exist and the browser's own focus color showed instead.
  • CategoryTag's remove button is named "Remove" and the label, like "Remove High", or removeLabel, where every tag's button was just "Remove".

Toggle group dots show the selected item

Fixed · Oct 1

In Toggle group's dots and Tab switcher's icon variant, the item that is on has its bg-primary dot again and rises when the group opens, and data-[state=on] styles on an item apply. ToggleGroupItem no longer takes variant or size, which the group always overrode, so remove them if you set them.

Combobox and Tenant picker name their search fields

Fixed · Oct 1

Combobox's search field is named by its placeholder unless you pass aria-label, and Tenant picker's index field is named "Find a workspace". Both had no name.

Command palette keeps focus on Shift Tab

Fixed · Oct 1

At the top level of Command palette, Shift Tab steps the scopes back and keeps focus in the field, as Tab does, instead of moving it to the scope tabs.

Command dialog names its field and returns focus

Fixed · Oct 1

Command's CommandDialog names its field with title, which takes over from an aria-label on CommandInput, and closing it returns focus to whatever had it when it opened rather than the page. Command palette returns focus the same way.

Ramson motion

New · Oct 1

Mark in motion is the Ramson mark as beat machine libraries. ramsonLibrary has the four shapes and the assembled mark with the hero's fling, and ramsonLitIsoLibrary stands the four outlined marks up as lit iso volumes. It is in beta.

Beat machine ships no brand

Breaking · Changed · Oct 1

Beat machine no longer ships ramsonLibrary, and lit iso no longer has the Ring, Nest, Leaf, and Bowl marks. Both moved to @gesso/ramson-motion, which installs ramson.ts where it was before. If you import ramsonLibrary from the beat machine's index, import it from libraries/ramson instead, and a saved story that placed one of those marks from lit iso names the library ramson-lit-iso now.

Docs sections

Docs · Oct 1

The sidebar is reorganized around what a reader is doing, and the old URLs redirect.

  • Getting started opens with an Introduction, then Installation, this changelog, and Status chips.
  • Theming is its own section with the token wiring, Chrome, Retune, Prefabs, and the Studio, which moved there from Brand.
  • Brand holds each brand's kit and its expression. Ascora has its five geographies, and Ramson has its mark in motion. Under them, Relief, Stage, and the beat machine are the engine both brands draw with.
  • Examples each end with the items they are built from, and link the full-screen beat machine and Ramson mark.
  • Every component page, from Button to Markdown editor, is rewritten to the new page shape, with a full props table, a demo per behavior, when to reach for a sibling instead, and an accessibility list written from the source, including what each one does not do yet. Field now shows how a label, a description, and an error are wired and announced, and every Forms page follows it.
  • Installation is rewritten as five steps from registering the namespace to choosing a look, with the prerequisites and the dependency catch that Button, Card, Badge, and Input do not pull in the tokens. Foundations and Theming are checked against the token CSS, which corrected the alpha scale uses, the ICS dark values, and the Ramson stroke and scrim notes, and the Prefabs page now compares all three looks.
  • The Studio export now matches the ICS prefab in dark, and a chromatic secondary writes the --sys-soft line the prefabs use, so the copied CSS repaints the same.
  • Button opens with a configurator, a live button above a panel for its variant, size, loading, and disabled props, which the page template now offers to components with enough prop combinations to need one.
  • Category colors shows its own New chip instead of Badge's status.

Command palette

New · Oct 1 · #33

Command palette is a two-pane ⌘K on Command and Dialog, lifted from Washi. Tab steps through scopes, a prefix like > jumps to one, a row can open a sub-list in place, and the highlighted row previews beside the list. Rows carry any item type, onQueryChange feeds a server search, and onKeyDown adds your own keys. It ships rankByName and PaletteMark for ranking and marking matches. It is in beta.

Command input slots

Changed · Oct 1 · #33

Command's CommandInput takes leading, trailing, and wrapperClassName, for a mode badge before the field and a key hint after it.

Brand kits

New · Oct 1 · #33

A brand tier now sits on top of the kit, so each brand comes from one place. The brand items are in beta.

  • Ramson is the press kit for the company mark and the internal tools. shadcn add @gesso/ramson-brand installs their marks in light, dark, and mono, font CSS for Outfit and Fraunces, and the Ramson prefab.
  • Ascora has its own kit with the o-ring and the wordmark, whose ring fades from purple to amber, and @gesso/ascora-brand brings the ICS prefab.
  • BrandMark draws any brand's marks, with an auto variant that follows .dark.
  • Every mark is hosted as SVG and PNG at gesso.ramson.tech/brand/<brand>, with a manifest and a zip, for email, decks, and agents to link to.
  • The Brand Studio moved to Studio.

September 2026

Block editor variables, image rows, and a hosted toolbar

Changed · Sep 24 · #32

  • Block editor takes variables, and authors can put {{key}} merge fields in the letter as chips that show their labels. They insert from a braces button in the text toolbar, by typing {{, or into link, button, and image fields, and a URL may now start with a variable.
  • With a list to check against, an unknown key or an unsupported tag like {{#section}} is an error in the issues panel.
  • Email blocks add an image row that puts images side by side in equal columns, and they stay side by side on phones.
  • BlockEditorToolbar takes variant="bare" and hideBlockActions, and toolbarContainer renders the toolbar into a host's own header, still driving the editor.
  • The toolbar's dividers render again. They had collapsed to nothing, and in a fixed-height editor they stretched and pushed the toolbar's controls under the canvas.

The block editor now depends on @tiptap/suggestion.

Drawer

New · Sep 24 · #31

Drawer is a bottom sheet you can grab. It rests only at the detents you give it, flicks to the next one, hands a scroll to the sheet at the body's top, and keeps its footer on the screen edge at every height. It is in beta.

Primary ink

New · Sep 24 · #30

text-primary-ink (--sys-primary-ink) is the brand as small text, mixed toward ink so it clears 4.5:1 on paper and on bg-selected in every prefab. See Color for when to use it over text-primary.

Markdown editor

New · Sep 24 · #30

Markdown editor is a block editor whose file is the markdown. Untouched blocks save back byte for byte and only real edits reach onChange. It reads wikilinks, (key:: value) fields as chips, block ids, tags, callouts, and --- cards you can drag to reorder, with a slash menu, a [[ note picker, and tasks that ink and sink. Links to notes, code editors, Notion, and the web each chip with their own icon and a hover card. Task states, highlights, AI runs, and tags take the category palette. It is in beta.

Category colors

New · Sep 24 · #30

@gesso/category-colors ships catChipStyle and catDotStyle on their own, so anything can color by category without installing cell fields. Cell fields now depends on it and re-exports the same functions, so nothing changes for an existing install.

Stat strip

New · Sep 23 · #29

Stat strip joins a row of metrics into one object, tiles set apart by the stroke width, each a label, a tabular number, and a hint like "41% of recipients". It is in beta.

Segmented bar

New · Sep 23 · #29

Segmented bar shows how a whole splits into parts on one 8px track, with status tones or category colors, an optional legend of counts and shares, and widths that ease when the values change. It is in beta.

Section

New · Sep 23 · #29

Section is a titled block of a page outside a form, with a description and header actions, in FieldLegend's type.

Description list

New · Sep 23 · #29

Description list lays out label and value rows as joined tiles, each with an optional action like an edit button.

PageHeader back, actions, and narrow screens

Changed · Sep 23 · #28

PageHeader picks up what the mass-email flow needed:

  • backStyle="inline" puts a 40px arrow left of the title, named by backLabel, and titleAccessory sits right after the title for a control like a rename pencil.
  • primaryAction holds the main verb. Below 36rem of header width, actions fold behind a kebab when you pass the same actions as menuActions MenuItems, and the primary action stays beside it. Without menuActions, the actions wrap onto their own line.
  • The title no longer squeezes to nothing at phone width, and a badge that no longer fits wraps under it. Wider headers keep the one-line layout.
  • title takes any node, and children render between the title row and the tabs.

PageHeader now depends on IconButton and Menu, so a refresh pulls those too.

Alert action

Changed · Sep 23 · #28

Alert takes an action after the text and ahead of dismiss, so a verb like Change no longer has to be positioned over the alert. Pass an AlertAction, a small secondary or ghost button that takes the alert's status hue, as the dismiss button now does too.

Confirm without a trigger

Changed · Sep 23 · #28

Confirm no longer needs a trigger when open is controlled, so a menu item or shortcut can open it. Focus returns to returnFocusRef, or to what held focus when it opened, instead of falling to the page.

RadioGroupCard

Changed · Sep 23 · #28

Radio group adds RadioGroupCard, an option in a bordered card with a title and a line of detail. The whole card selects it, and it styles itself from the radio's checked and disabled state.

Table alignment

Changed · Sep 23 · #28

Table heads and cells take align, so a numeric column can right-align its head along with its values. text-right could not move a head's label, sortable or not.

IconButton sizes

Changed · Sep 23 · #28

IconButton takes size: sm (36px, the default), md (40px), or xl (56px), on the same heights as Button. It no longer needs a size class to sit beside default-size actions.

Progress size and value

Changed · Sep 23 · #28

  • Progress takes size="sm", an 8px track with the fill inset 2px. A height class alone collapsed the inset fill to nothing.
  • Progress now hands its value to Radix, so the bar announces aria-valuenow and reads data-state="loading" or complete instead of always indeterminate.

TenantPicker and Input fixes

Fixed · Sep 23 · #28

  • TenantPicker's trigger kept losing its data-slot to the popover it opens. It now holds tenant-picker-trigger in every craft, so apps can size the trigger through it.
  • Input paints a destructive border under aria-invalid, like Textarea and Select already did.

Recipes and notes from mass-email

Docs · Sep 23 · #28

  • Field shows a Checkbox or Switch row with its description and error wired to the control.
  • Table shows a total and its parts as one block of rows.
  • Switch explains the development-only flushSync warning a Switch in a form logs while a Popover is open.
  • Installation explains why shadcn add asks to overwrite dependencies you already have, and how to keep re-adds quiet.

Block editor spacing presets

Changed · Sep 23 · #27

The block editor inspector picks section padding and spacer height from preset rows, with Custom for anything else, and sections gain a gap setting for the space after each block. Every gap and padding snaps to a 4px grid, custom values included. Existing documents compile exactly as before.

Stepper numbers every step

Breaking · Changed · Sep 23 · #26

Stepper drops the front counter so the rail spans the full width in equal columns. Every step shows its number and name, finished steps swap the number for a drawn check and stay clickable, and upcoming steps render grayed out as disabled buttons. completeLabel is removed; drop the prop if you set it.

CheckMark

New · Sep 23 · #26

CheckMark is a stroke check that draws itself, down the short leg and up the long one. checked drives it, and delay syncs it to a parent's motion.

Changelog page

Docs · Sep 22

This page, and the rule that every merged change adds an entry here. HealthFace and Stepper also pick up the New chip they should have shipped with.

Stepper

New · Sep 22 · #24

Stepper is a step indicator without nodes or check marks. Steps are pills on a rail and the current one takes the width, and a worm stretches into the next step on the Tabs timing and covers the rail when the flow is done. The rule variant sits on the same hairline as TabsList, so it can take a tab row's place in a PageHeader without moving anything.

HealthFace

New · Sep 22 · #23

HealthFace is Phosphor's filled smiley with a mouth on one spring, so on track, at risk, and off track morph into each other. The property chip demos use it in place of the health dot.

Ramson greens, a control track, and one alpha scale

Changed · Sep 22 · #22

  • The Ramson prefab primary drops to pine, a green off-black, and its fills, hover, and focus ring tint green instead of warm gray.
  • New --sys-track (bg-track, border-track) for unfilled control edges. Switch, Checkbox, and Radio use it, so unchecked controls clear 3:1 on every surface.
  • Disabled Switch, Checkbox, and Radio keep their mark readable instead of fading the whole control. Switch also gets an even thumb inset.
  • Every alpha in the registry now points at a --sys-alpha-* step. Scrims move to bg-scrim, the shadow ink, so no prefab tints an overlay and dark mode stops washing out.

Lit-iso shading and mark shapes

Fixed · Sep 22 · #21

Lit-iso volumes on the beat machine no longer clip to white on paper surfaces, a mixed stage shares one lighting rig, and four Ramson mark shapes land as extruded volumes.

Beat machine

New · Sep 21 · #17

The beat machine authors stage stories on the Relief stage. Beats can pose objects on a grid and name a transition, and the stage builds geometry once and mutates it per frame.

Mobile sidebar tab bar and drawer

New · Sep 19 · #20

The Sidebar gets two opt-in pieces for phones: a bottom tab bar and a drawer the page shoulders over. One SidebarTrigger still drives both.

Table and cell field accessibility

Fixed · Sep 19 · #19

Cell fields ship their own focus chrome, and Table fixes padding and makes sortable heads real buttons.

ChipTrigger

New · Sep 17 · #18

ChipTrigger is one chip surface that sits under any picker root: dropdown, combobox, popover, or date picker. FilterChip now composes it.

Relief

New · Sep 16 · #13

Relief builds isometric technical illustration from data, so a drawing holds at every angle and follows the theme. Six items land the scene plus its geography and composition tiers.

Status chips

New · Sep 16 · #16

Registry items can be marked beta or new in registry.json, and the sidebar derives its chip from that. Status chips covers the lifecycle and the pnpm status:* scripts.

React Email compile path

New · Sep 16 · #12

The block editor compiles email through React Email and previews it at real viewport widths. Canvas and compile share one theme.

Pagination

New · Sep 14 · #15

Pagination is fully controlled page controls with an optional page-size select, and it pages without a known total.

Cell fields replace the data grid

Breaking · Changed · Sep 14 · #14

Inline cell editing moves out of the data grid into its own item, @gesso/cell-fields, usable from Table, TanStack, or your own markup. @gesso/data-grid is retired; installs that used it move to cell fields plus Table.

Kit build-out

New · Sep 2 to Sep 11 · #1 to #11

The first run of PM-driven components, batched.

  • The docs sidebar splits into Getting started and component groups, and the kit gets a landing page.
  • PM primitives: Calendar, Command, Sidebar, RecordList, ToggleGroup, and more. Tabs becomes an edge rule with a stretching mark, and toasts lose their border.
  • IconButton, Confirm, WorkMark, and PriorityBars.
  • The --sys-alpha-* wash scale, with selected landing on a step.
  • AppShell chrome and TenantPicker.
  • Sidebar collapse goes fluid, with a primary stroke, a fade variant, counts, and a ⌘B guard.
  • The shared block editor and email blocks, both in beta.

August 2026

Foundations

New · Aug 21 to Aug 29

Where Gesso starts, batched.

  • The dry kit: four token tiers (primitives, brand, system, bindings), the Washi and ICS prefabs, and the first components on shadcn's registry.
  • The token architecture reworked around chrome lightness and paper temperature, with the component and docs build on top.
  • The Ramson prefab.
  • The docs look switch moves into a popover beside the theme toggle.
  • The kit takes the name Gesso, the registry is served from gesso.ramson.tech, and Vercel builds it on every deploy.

On this page

October 2026Segmented well cornersVistulaImage field as a tileVariable search matches word by wordImages say when they'll look soft, and fill their own alt textImage fieldPictures drop, paste, and show in their block's shapeA header can show the brand's logoTime pickerEmail blocks say image, not picture or photoVertical tabs can group a run of tabs under a labelVariable lists read by name, with an icon per groupA new variable can stay plain textA long group in the fields panel folds to one lineA highlight can ring a whole group of variablesA description row's parts line up on one lineThe block editor says your word for a variableA section's actions stay beside its titleA stat can set at a medium sizeCreating a variable can ask for details firstA description row's action sits at its top rightThe issues chip sits beside undoThe canvas spaces blocks like the emailA copy that reads like what it sendsA note beside each variableTemplates and copies in the block editorCreate a variable while writingSmall empty stateColor pickerBrand moments in email blocksPictures in block settingsVariables as chips in block fieldsMentions, page blocks, and other app extensionsEmail card, image row fit, brand snapshotGalleryEmail letterhead, spec table, signature, footerInteractive cardInfo can take the brandSurfaces, rebuilt for dark modeTwo borders, by jobShadows that show in darkSelected rows and togglesPanels sit on the wellA lighter placeholderBadge and Chip take a category colorWarning text stays in inkA look can warm the category huesUse a toneA lighter trackThe warning toast takes the tintA lighter info solidA yellow category, and a yellower warningDeeper success and info solidsA stronger tone fillChips read tone, and the subtle tints are goneOne redThe second hue is the support toneBrand ink is the primary toneStatus without its own tokensOne way to say a statusToneStatus from the categorical huesAlpha scale in the primitivesNavigation, feedback, and layout fixesData display fixesOverlays fixesForms fixesToggle group dots show the selected itemCombobox and Tenant picker name their search fieldsCommand palette keeps focus on Shift TabCommand dialog names its field and returns focusRamson motionBeat machine ships no brandDocs sectionsCommand paletteCommand input slotsBrand kitsSeptember 2026Block editor variables, image rows, and a hosted toolbarDrawerPrimary inkMarkdown editorCategory colorsStat stripSegmented barSectionDescription listPageHeader back, actions, and narrow screensAlert actionConfirm without a triggerRadioGroupCardTable alignmentIconButton sizesProgress size and valueTenantPicker and Input fixesRecipes and notes from mass-emailBlock editor spacing presetsStepper numbers every stepCheckMarkChangelog pageStepperHealthFaceRamson greens, a control track, and one alpha scaleLit-iso shading and mark shapesBeat machineMobile sidebar tab bar and drawerTable and cell field accessibilityChipTriggerReliefStatus chipsReact Email compile pathPaginationCell fields replace the data gridKit build-outAugust 2026Foundations