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.
A header can show the brand's logo
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.
EmailBrandtakes an optionalaccentColorfor 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.
imageLibrarygives 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. PictureFieldandPicturePlaceholdergive 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.
VariableInputandVariableTextgive a custom block the same field and the same canvas chips, anddocumentVariableslists 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(andsm:pr-16withcards) did, or mark a box around itdata-markdown-gutter-edgeand the handle hangs just outside it. - Markdown editor takes
mentions, so@opens a picker that writes[@Name](mention:<id>)as a person chip, andMentionChipdraws one in read-only markdown. A line holding only[[<id>]]is a page block thatpageBlockdraws as your card and that drags by the card.taskBoxdraws the box for a task your app owns, a field takescontentandappearance: 'inline', andLinkChipdraws 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, andhashTagColorleaves 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 fromemail-blocks/serialize, and the rich-text and variable helpers are inblock-editor/rich-text-modelandblock-editor/variables-model, still exported from the editor entry. - Work mark has a
cancelledstatus, a muted disc with a cross, andshape="square"for a task box. ARecord<WorkStatus, …>of your own needs acancelledentry. - 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,trailingfor meta the display menu does not govern, and a node astitle. 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", andrenderTriggerfor a trigger of your own, such as a Chip trigger. - Scroll area takes
orientation, for a sideways strip, andviewportReffor 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
pushandwell, 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
imageSrcto 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.
canvasStyleandframeStylepaint 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, usedanger. - 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.
Gallery
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.
Email letterhead, spec table, signature, footer
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%.
Navigation, feedback, and layout fixes
Fixed · Oct 2
- Sidebar's desktop rail is a
<nav>named "Sidebar",isActivesetsaria-current="page"throughasChildtoo,SidebarTriggerreportsaria-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
indexmarks the current workspace witharia-current, and insealsthe 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
onClickor your own control withasChild, 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 takesdismissLabeland 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
asfor the title's level. - Skeleton takes a
labelthat 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
ItemGroupis 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
jtyped 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.
PopoverListboxis 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, withscrollLabelto 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 atitleor anaria-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 itsrefand SVG props. - Segmented bar draws its segments in
CanvasTextinside 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
asChildmakes it a link or a button, and then shows a focus ring. - Card's
CardTitletakesasto 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
TooltipProvideruses that provider, so the provider'sdelayDurationapplies 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
hrefnorasChild, since that trigger renders an<a>the keyboard cannot reach. - Icon button takes a
TooltipProvider's delay unlesstooltipDelayis set, so it joins a row of grouped tooltips.
Forms fixes
Fixed · Oct 1
- Field's
FieldErrorwith severalerrorsrenders 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/inputnow 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-14on 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 inCanvason theGrayTextfill, 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-destructivetrack foraria-invalid, like the other controls. In forced colors an off thumb isButtonTextrather than the track's own color, and a disabled switch that is on fills withGrayTextunder aCanvasthumb, 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 avaluekeeps the same value when it is picked and no longer matches a search for "selected". - Calendar's month menu uses the calendar's
localerather than the browser's language, and a day'sdata-dayis an ISO date, so neither differs between the server and the browser. Withdir="rtl"andnavLayout="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-softline 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-brandinstalls 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-brandbrings the ICS prefab. - BrandMark draws any brand's marks, with an
autovariant 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.
BlockEditorToolbartakesvariant="bare"andhideBlockActions, andtoolbarContainerrenders 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 bybackLabel, andtitleAccessorysits right after the title for a control like a rename pencil.primaryActionholds the main verb. Below 36rem of header width,actionsfold behind a kebab when you pass the same actions asmenuActionsMenuItems, and the primary action stays beside it. WithoutmenuActions, 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.
titletakes any node, andchildrenrender 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-valuenowand readsdata-state="loading"orcompleteinstead of always indeterminate.
TenantPicker and Input fixes
Fixed · Sep 23 · #28
- TenantPicker's trigger kept losing its
data-slotto the popover it opens. It now holdstenant-picker-triggerin 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 addasks 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 tobg-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.