Project
Changelog
Every release of Cynosure, newest first.
Minor ChangesLink to "Minor Changes"
- Badge: leave out the text for a status dot alone in the tone's own colour: with a
labelit is an image named by it, without one it is decoration hidden from assistive technology. - Button:
--cy-button-hover-bgand--cy-button-hover-inktokens set the fill and text colour on hover, for one button whose hover differs from its variant. - Button:
--cy-button-square-widthsets the width of a square (icon-only) button such as an IconButton; it defaults to the height. TitleBar widens its window controls with it. - Card:
titleanddescriptionput a header above the content (headingLevel, default 3), sharing its row withactions. New token--cy-card-title-size. - Every palette has
seriesColors, eight chart colours from the accent that read 3:1 on the page (--cy-color-series-1to-8), andresolveColorsandswiftChartThemegive canvas engines and swift-chart concrete colours, without a chart dependency. - ChatMessage is one message in an assistant chat: the person's at the end, the assistant's after an avatar, with a streaming caret, a danger tone, wide content and actions shown on hover or focus.
- CodeBlock:
actionsputs your own buttons in the header before the copy button, such as "Insert into editor". - Combobox and CommandPalette mark the typed text in their option labels with HighlightedText (in CommandPalette, each typed word). Options are still named by their whole label.
- CommandPalette: a
filter(command, query)prop replaces the built-in search with your own (a fuzzy matcher's{ score, positions }ranks the results and marks exactly those letters). Search results are now one flat list in rank order with each command's group shown as a small tag, as recent commands are; browsing without a query still lists commands under their group headings. - ContextMenu: one menu can serve many targets (the rows of a list, the tabs of a tab list) through the render prop's
targetFor(key);labelanditemsmay be functions of the key, andonSelectandonOpenChangereceive it. Tabs and Tree use it instead of their own copies. - DataGrid is a read-only grid for large results: rows and columns render only where they are in view (a hundred thousand rows by fifty columns stay quick), under a sticky header, with the APG grid keyboard.
- Disclosure:
edgedraws a coloured inline-start edge in a tone or any colour, as Card's does, andtrailingputs a Badge or a Button at the end of the summary row, outside the trigger button. New token--cy-disclosure-edge-width. - ErrorBoundary: a new component that catches errors thrown while its children render and shows a fallback in their place (a danger Notice with "Try again" by default, or your own
fallback(error, retry)), withresetKey,onErrorandlabel. - New Field tokens
--cy-field-control-border,--cy-field-control-bg,--cy-field-control-radiusand--cy-field-control-sunken-bgrestyle the box of every field at once: TextField (and the fields built on it) and HotkeyField read them under their own tokens. - Field and every control built on it (TextField, Select, Combobox, TagInput, PathField, NumberField, DateField, TimeField, ColorField, TextArea, HotkeyField; the
FieldLayoutPropstype):layout="split"puts the label and description at the start and the control at the end of one row (a settings row), moving the control under the text when narrow, andwidth(sm,md,lg,full) lines the controls of a column up. New tokens--cy-field-control-width-sm,-md,-lgand--cy-field-split-text-min. - FileDrop:
onPickreplaces the browser's file dialog with an app's own picker, as PathField'sonPick(the button is busy until its promise settles; chosen files are checked and arrive inonFiles, chosen paths inonPaths), and a controlledvaluelists the chosen files or paths in the zone, each with a remove button that callsonRemove(item, index). New token--cy-file-drop-file-background. - PanZoomCanvas takes a
handleprop, as ScrollArea and VirtualList do, and its handle hascenterOn(American spelling, as the rest of the API).refstill gives the handle andcentreOnstill works; both are kept for existing code. VirtualList'sscrollToIndex(index, { align })takes an options object, as ScrollArea'sscrollIntoViewdoes; the earlierscrollToIndex(index, "center")form still works. - New HighlightedText: text with the parts that matched a search marked, from a fuzzy matcher's
positionsor every occurrence of aquery(one term or several), ignoring case. It reads as one piece of text to screen readers and marks matches with the system colours in forced-colours mode.textMatches(text, query)filters by the same rule, and Combobox, TagInput, TextArea's mentions, Tree and CommandPalette use it, so what a list keeps and what it marks always agree. - HotkeyField records a keyboard shortcut for a keybindings page, written as
useHotkeyreads it;hotkeyFromEventturns a key press into that string, anduseHotkeynow also acceptsspaceas a key name. - Icons: a new entry,
@arshad-shah/cynosure/icons/by-name, exportsiconByName(name)and theiconsrecord of every Facet icon by its name, for names that arrive as strings (plugin manifests). It brings every icon with it, so the main entry and@arshad-shah/cynosure/iconsnever import it and stay tree-shaken. - Facet icons: 24 new icons, each with search words and a motion: file-question, file-search, file-plus, window-maximize, window-restore, panel-right, scissors, text-select, clipboard-paste, function (exported as
functionIcon), rows, square-plus, cable, boxes, shield-question, gauge, check-check, power-off, loader, party-popper, rocket, hourglass, languages and monitor-down. - Stack, Inline and Grid take
grow,shrink,fill,minSize="0"andoverflow, for app shells whose middle takes the free space and scrolls; GridItem takesoverflow. - New ListRow and ListRows: interactive list rows (notifications, activity, saved queries, connections) with a leading slot, a title and two meta lines, a trailing slot, actions shown on hover or focus (always on touch), and
active,unread,dimmedandtonelooks. The rows are buttons or links with one Tab stop and arrow keys, and Right moves into a row's actions. - New
LiveRegionfoundation anduseLiveMessagehook: the one hidden live region every component announces through, so the same message twice is spoken twice. FileDrop, TagInput, Tabs, VirtualList and SortableList now announce a repeated message again (a second "1 file added"), and every component's hidden region carriesaria-liveandaria-atomic. - LoadingDots:
variant="spinner"draws theloadericon turning, icon-sized and in the current colour, with the samesize,tone,color,speedandlabel; reduced motion leaves it still. - Menu:
open,defaultOpenandonOpenChange, so an app can open a menu from elsewhere, such as a keyboard shortcut. - Menu:
--cy-menu-offsetsets the gap between a menu and its trigger (default--cy-space-2), and Menubar's menus read it too instead of a fixed 4px. - Menu, ContextMenu and Menubar: an item with
itemsopens a submenu beside it, nested to any depth, following the APG menu keys (Right/Left, RTL aware), opening on hover after a short delay that tolerates diagonal travel, and closing the whole tree on a choice;onSelectreceives values from any level. - Menu, ContextMenu and Menubar: a section's
labelis optional; a section without one is set apart by separator lines instead of a named group. - Menubar: a new APG menubar for a desktop app's application menu, a row of titles each opening a Menu (
label,menus: { label, items }[],onSelect,size). One menu is open at a time; Left and Right, hovering and tapping move between menus. - Named themes:
createTheme({ mode })fixes a theme to light or dark (theThemeModetype) so the colour-mode switch does not flip it, anddefineThemeandcreateThemeRegistrycheck plain-JSON theme definitions (plugin themes) at runtime, rejecting bad input or unreadable text withCynosureThemeError.useCynosureTheme()returns the theme, palette and resolved colours where it is called, for canvas and chart code. - Notice:
variant="solid"paints the tone's solid colour with the text colour made for it, andsize="sm"tightens the padding and keeps the title and message on one line when they fit. New token--cy-notice-padding-sm. - OptionList: options take
trailingcontent (a shortcut or a count at the row's end) anddimmed, and the list draws afooteraction pinned under the options ({ action, id, active, onActive, onRun }). Select and Combobox options taketrailinganddimmed, and both fields take afooteraction ({ label, icon?, onSelect }) that the arrow keys reach after the last option. - New OverlayFrame: a frame whose content fills it, with an
overlaypinned at the bottom centre that stays put while the content scrolls (tokens--cy-overlay-frame-offsetand--cy-overlay-frame-layer). ScrollArea and VirtualList draw theiroverlaywith it. - PanZoomCanvas takes
claimPress: asked about each press with its point in canvas units, it can claim the press to drag a node (children or painted content) instead of panning, with the drag reported in canvas units. - PanZoomCanvas's
renderScreengets the screen'sdpr,canvasPropsto size a<canvas>in device pixels, and atransformto draw in canvas units; it draws again when the window moves to a screen of another density. - PanZoomCanvas takes
minandmaxfor its zoom limits in percent (25 to 800 unless given), and ZoomControls takes the sameminandmax, offering only the presets between them. - PanZoomCanvas takes
overlays: controls in its four corners (start and end follow the page direction) that stay put while the content pans and zooms, keep their own pointer, wheel and keys, and never start a pan. The minimap now sits in the bottom-end corner under that corner's overlay. - PathField: a new component for a file or folder path, a text field with a Browse button that asks your app's picker (
onPick(): Promise<string | null>) and a clear button, withvalue/onChange,nameandformfor forms andlabels. A picker that fails leaves the path as it was. - Popover:
initialFocus(first,panelornone, which leaves focus where it is so a hint can open beside a field being typed in) andwidth(content,triggerto be at least as wide as the trigger or anchor, orfixedfor exactly the size preset). An anchor element now counts as inside, so pressing it no longer closes the panel. useRovingFocustakescurrent: trueto treat the item markedaria-currentas the selection, so the Tab stop starts on (and follows) the open entry of a list of links or buttons.- RowActions: the buttons at the end of a row, hidden with a mouse until the row is hovered or holds focus or a menu of theirs is open, always shown on touch screens, and in the Tab order only on the row that holds the Tab stop. ListRow, Tree and ChatMessage render their actions with it, so they share one reveal rule (Tree's actions now fade by opacity like the others').
- New ScrollArea: a box that fills its parent and scrolls, with
direction,follow,onScroll,onAtBottomChange, anoverlaythat stays put (its root is an OverlayFrame), and ahandlewithscrollToEndandscrollIntoView. With alabelit is a named region and a tab stop while there is something to scroll. - SearchField:
trailingshows status text inside the field's end, such as a match count ("3 of 12"), announced politely when it changes. New tokens--cy-search-field-trailing-colorand--cy-search-field-trailing-font-size. One vocabulary and one look across lists and fields:
- Select and TextArea now read the field skin (
--cy-field-control-border,--cy-field-control-bg,--cy-field-control-radius, and for a read-only TextArea--cy-field-control-sunken-bg), so restyling fields once restyles every field's box. - VirtualList takes
selected/defaultSelected, like Tree, Table and DataGrid.selectedKeys/defaultSelectedKeysstill work but are deprecated. - Tabs reveals the selected tab with the shared
alignedScroll. A padded tab wider than the strip now shows its start, asscrollIntoViewdoes.
- Select and TextArea now read the field skin (
- SideNav works as an activity bar:
footerpins entries (Settings, a notifications action) to the bottom,placement="end"puts the current indicator and the rail's tooltips on the side of a right-hand rail, and in the collapsed rail an item's badge sits on its icon's corner. - Button, IconButton, Select, TextField, ProgressBar and SegmentedControl take
size="xs"(the newDenseSizetype): about 20px tall (the new global--cy-control-height-xs,control.height.xsin a theme), with smaller text, icons and padding, for dense desktop chrome. Button and TextField also take--cy-button-height-xs,--cy-button-padding-inline-xs,--cy-text-field-height-xsand--cy-text-field-padding-inline-xs. Only the look shrinks: the hit area stays at least 24px, and 44px on coarse pointers. - New SortButton: the button in a sortable column's header, with the sort's icon. Table and DataGrid render it, so their sort headers share one look (Table's
cy-table-sortand DataGrid'scy-data-grid-sortclasses are gone). - SplitPane:
collapsed,defaultCollapsedandonCollapsedChangecollapse a pane from a menu or button and restore its remembered size,onResizeEndfires once when a drag or key resize ends, andanchor="end"gives the size to the end pane (a right sidebar or bottom dock that keeps its pixels). Collapsing no longer reports a size of 0 throughonSizeChange: the size is kept. - StatusBar and StatusBarSegment: the bar along the bottom of a desktop app, with start and end clusters of segments that show a tone, an icon or a status dot and monospace text, and as buttons open a Popover above them; arrow keys move between the clickable ones.
--cy-status-bar-soft-mixsets the tint of soft segments, static or clickable. - Tab: a new
closeLabelprop names a closable tab's close button. Without it, a tab whose label is not a plain string (a badge, bold text) is named by the text it shows instead of its rawvalue. - Tabs:
contextMenuandonCommandon a Tab give it its own commands (Close others, Close to the right…) in a ContextMenu opened by right-click, a long press, Shift+F10 or the context menu key. A chosen command goes toonCommand(value), the name Tree uses for its commands. - Tabs:
dirtyon a Tab marks unsaved changes with a dot that turns into the close button on hover or focus; the close button's name and the tab's description say the changes are unsaved. - Tabs:
trailingon a TabList places content, such as a "New tab" button, right after the last tab and outside the tab list. - Tabs: a label longer than
--cy-tabs-label-max-width(default 20em) is cut short with an ellipsis, and the whole text shows in a tooltip on hover or focus. - Tabs: a middle-click on a closable tab closes it, without starting the browser's autoscroll.
- Tabs:
scrollableon a TabList adds chevrons at each end, shown only while there are more tabs that way, and scrolls the list sideways with a vertical mouse wheel. - TagInput: Backspace in an empty field now takes the last tag back into the field to edit (announced through the new
labels.editing) instead of only removing it.renderTag(tag)draws custom tag content, andiconStartputs an icon in the field. - TextArea: mentions.
trigger(such as "@") andsuggestions(a list, or a function of the query returning items or a promise) open the shared drop-down list at the start of a word; arrows move, Enter or Tab inserts the value and a space, Escape closes it, andonMentionreports the choice. The list is named by the field's label; newlabels.loading. - TextField:
boxRefgives the field's box (the input with its icons, addons and buttons), so a list or popover can drop from the whole field. Combobox and TagInput anchor their lists with it. - Text:
wrap="pre"keeps spaces and line breaks as written, andwrap="break"breaks long ids and URLs anywhere instead of overflowing. - TitleBar: the title bar of an installed app that draws its own (frameless Electron windows, Window Controls Overlay). The whole bar drags the window while the controls inside stay clickable, and it takes
leading,trailing,leadingInset, anoverlaymode,onHeightChangeand windowcontrols(minimize, maximize or restore, close). - Toast:
toast({ id })with the id of a showing toast updates it in place instead of adding another,loadingshows a spinner and holds the toast until it is updated, andonDismiss(id, reason)says whether it timed out, the user closed it ordismiss()did. - Tooltip:
disabledturns the tooltip off without remounting its trigger, anddescribe={false}shows a tip that repeats the trigger's name without reading it to screen readers again;whenTruncatedturns it on only while text is cut short (measured with one ResizeObserver) and impliesdescribe={false}. Tabs, StatusBar and DataGrid use it, so StatusBar and DataGrid no longer read a cut-short text twice and DataGrid's tip shows only when the cell is cut. - Tree, for schema explorers: items take
iconTone/iconColor,trailing, a typeddatapayload,actions(buttons on hovered, focused or selected rows, in the Tab order after the focused row) and acontextMenu(one ContextMenu beside the tree), all reporting throughonCommand;kind: "group"heading rows that count their children and are never selected;filter(a string or a function returning match positions) shows matches with their ancestors and marks them;loadingIds,errorIds,onRetry,renderErrorandrenderEmptyfor loading driven by a store; and ahandlewhosereload(id)runsloadChildrenagain. - VirtualList takes
onAtBottomChangeand anoverlaypinned over the bottom of the list (a "3 new" pill), and follows its end through the same helper as ScrollArea. The list now always sits in an OverlayFrame.
Patch ChangesLink to "Patch Changes"
- Accordion: a controlled, collapsible single Accordion now closes its open section when
valuebecomes undefined, instead of falling back to its own state and staying open. - Select, Combobox, TagInput, TextArea's mentions and CommandPalette keep their active option the same way: kept while it is still in the list, else the first usable one, and always scrolled into view (TagInput's suggestions did not scroll before).
- Popovers, menus and drop-down lists flip to the other side when their content grows after opening and no longer fits.
- AppShell: the navigation drawer closes when the shell widens, so it no longer pops open by itself when the shell narrows again.
- Slider and NumberField write
aria-valueminandaria-valuemaxwithout exponent notation (0.0000005, not 5e-7), as they already didaria-valuenow. - Calendar no longer risks a hydration mismatch: the device's date, language and weekend settle in the browser before the first paint, and a server draws no day as today and uses
locale(or English) and Monday-first weeks until then. - Calendar builds its date formatters and reads each locale's week once instead of on every render (about 40 Intl objects per render before), so DateField stays light while typing.
- A Checkbox with
indeterminatestays mixed after a click or Space while the prop is still true, instead of the browser clearing it. - A controlled ColorField keeps its saturation and hue at black and through 8-bit rounding: bringing the brightness back up returns the colour you had, not grey. A
valuethe parent changes itself is still read afresh. - ColorSwatches no longer chooses a swatch when Tab lands on it (so a custom colour in ColorField survives tabbing through), and a click calls
onChangeonce instead of twice. - Combobox with
allowCustomkeeps the chosen option's value when you leave the field (it used to commit the label), and options that load after the field mounts now show the chosen label instead of blank text that cleared the value on blur. - CompareSlider calls
onChangeonly when the divider actually moves, not for Home at the start, arrows past an end or a pointer resting on one spot. <CynosureTheme contrast="normal">without athemenow keeps the normal palettes on an OS set to more contrast, instead of inheriting the high-contrast tokens from the root.- DateField submits its date, not the text being typed: a form will not submit while the text is not an available date, and a form reset brings back
defaultValue. - DateField's calendar now opens on the chosen day inside a shadow root too; it used to find the calendar by id in the document, which missed it there.
- The FloatingToolbar and HoverCard offset tokens accept any length (
rem,calc()), and the offset is read once when they open instead of on every render. - Escape in a Menu or Popover opened from a FloatingToolbar or DockedToolbar now closes that menu first; the next Escape leaves the bar.
useHotkeynow matches Option shortcuts on macOS (alt+k) and shifted digits (shift+1) by the physical key, and accepts+as the key (mod++).- TextField, Select and SegmentedControl at
size="lg"show large (20px) icons, like a large Button, instead of medium ones, so a row of large controls shows one icon size. - NumberField reads back its own text in every locale: other digits (Arabic-Indic in
ar-EG, Devanagari…), currency text with dots such as "1.234,50 kr.", unit text and accounting brackets. Edits there are no longer undone on blur. - ProgressBar: documents the slim bar in a row of text (
labelHiddenwithoutshowValue), with a story that checks it stays named, takes the space between its neighbours and fills from the reading start. - Reduced motion no longer stops the animations of host elements whose class merely contains "cy-" (such as
.privacy-banner); only Cynosure's own elements stop. - A Menu or Popover inside a Toolbar keeps its own arrow keys, and the toolbar keeps its Tab stop while one is open.
- Escape in a SearchField while an input method is composing (Chinese, Japanese, Korean…) now only cancels the composition; it no longer clears the query or leaves the field.
- In an open Select, PageDown and PageUp stop on the last or first usable option when the end option is disabled, instead of losing the active option.
- Selected rows in Tree, VirtualList and DataGrid use the global
--cy-color-accent-soft, the same tint as Table's selected rows (it was a slightly lighter mix of the accent written into each stylesheet). - Inside a shadow root, closing a Popover, Menu or other layer returns focus to its trigger, a modal Popover keeps Tab inside it, and dismissing a focused Toast moves focus to the next toast instead of leaving the stack paused.
- SignaturePad draws again at the new density when the window moves to a screen of another pixel density (or the browser zooms), so the signature stays sharp; it measures through the same helper as PanZoomCanvas.
- SignaturePad draws faster: finished strokes keep their outline, the canvas is reallocated only when its size or pixel density changes, and the stroke in progress is drawn once per frame without re-rendering. What it draws and reports is unchanged.
- A controlled Slider
valueoutsidemin/maxnow shows at the nearest end: the thumb,aria-valuenowand the arrow keys stay in range. - Slow trackpad pinches now zoom DocumentViewport and PanZoomCanvas: Ctrl/Cmd+wheel steps under a whole percent add up instead of being rounded away.
- SortableList: a keyboard move is cancelled when focus leaves the handle, instead of staying picked up. Dropping applies the move to the items as they are now, so items the parent added during the move are kept.
- Tooltip, Combobox, TagInput and the components that float beside an anchor no longer log "useLayoutEffect does nothing on the server" when rendered on the server with React 18.
- Slider and NumberField round to the decimals of both
stepandmin, somin={0.5} step={1}reaches 0.5, 1.5 and so on, and steps written as1e-7no longer round to 0. Their ARIA and form values are written without exponent notation. - Table: selecting rows no longer drops selected ids that are not in the current
rows, so a selection survives paging and filtering. The select-all acts on the rows shown. - Tabs: tab values that differ only in punctuation, such as "a b" and "a_b", no longer share an element id, so each tab controls its own panel.
- TagInput's arrow keys between the input and the tags follow the reading direction, so in right-to-left text Arrow Right moves into the tags.
- TextArea fits its height again when its width changes (a resized window, a collapsing sidebar), so rewrapped text neither scrolls nor leaves empty lines.
CynosureThemeno longer logs React 18'suseLayoutEffectwarning when rendered on the server.- TimeField submits its 24-hour value, not the text being typed: a form will not submit while the text is not an allowed time, and a form reset brings back
defaultValue. - TimeField's arrow keys now keep the minutes on the
stepgrid: with a step of 15, Up from 09:32 goes to 09:45 (not 09:47) and Down to 09:30. - Swiping a Toast away now runs through the shared drag handling: Escape mid-swipe puts it back and taps on its buttons stay taps. A leaving toast (and a closing Dialog) waits for its longest exit transition, delays included.
- Tree: ArrowRight and ArrowLeft no longer move the tab stop onto a disabled row (nor does a selected disabled row take it), so the tree always stays in the Tab order. Expanded and selected rows are looked up through Sets, so large trees render faster.
- Type-ahead in Select, Menu, Tree and VirtualList now finds the first item when nothing is active yet, instead of skipping it.
"use client"now marks only modules that run React, sothemeInitScript(),qrToSvg()and the zoom and layout helpers can be called from React Server Components.- VirtualList: moving rows with Alt+arrows or a drag is linear in the list's length (it was quadratic, so a large selection in a long list froze the page), and scrolling no longer re-keys every item or re-measures every wrapped row.
Minor ChangesLink to "Minor Changes"
- New
@arshad-shah/cynosure/astroadapter, an Astro integration that themes the page on the server:cynosure({ theme })adds the product theme as CSS on:rootand Cynosure's stylesheet to every page, and the saved-mode script inline in<head>(initScript, default on);htmlAttributes({ mode, contrast })gives<html>its mode and contrast attributes. Seedocs/adapters.md. - New
Choice: the row a choice control sits in (the control, its label and description, the whole row clickable), withstrikeWhenCheckedand the tokens--cy-choice-gap,--cy-choice-done-colorand--cy-choice-strike-width. Checkbox, Switch and RadioGroup's options are now Choice rows, and each owns its control's look in its own stylesheet:styles/checkbox.cssholds only the box, the newstyles/switch.cssthe switch,styles/radio-group.cssthe radio, andstyles/choice.cssthe shared row (it used to be ChoiceBase's stylesheet, nowstyles/choice-base.css). Import Choice's stylesheet with Checkbox's, Switch's or RadioGroup's when you load stylesheets one by one (styles/manifest.jsonlists them). Props, tokens and the look are unchanged;--cy-checkbox-done-colorand--cy-checkbox-strike-widthnow set the row's done tokens.ChoiceControlPropsandChoiceInputare exported for building new choice controls. - New
DeviceFrame: a phone, tablet or browser window drawn in CSS around a screenshot, a video or a live demo. The screen keeps its ratio at any width and clips its content to its corners,orientationturns a phone or tablet, and the browser bar shows an optionalurl. It is decoration by default and adds nothing to the accessibility tree; withlabelit is a named figure. - TextField: new
addonStartprop (controls inside the start of the field, before the input) and--cy-text-field-numerictoken (the typed text'sfont-variant-numeric). Withtype="search"TextField hides the browser's own search decorations itself. DateField, NumberField and TimeField set--cy-text-field-numeric: tabular-numsinstead of restyling the input, and SearchField no longer has a stylesheet of its own:dist/styles/search-field.cssis gone (its rule is intext-field.css). ColorField is now a TextField with the swatch button inaddonStart, and draws its hue and opacity strips through Slider'strackContentinstead of restyling the slider; it looks and behaves as before. - The root entry exports the types components' props are written in:
IconData,IconNode,IconMotion,IconCategoryandBrandInfo(no need to reach into/iconsfor a prop's type), and the layout typesSpace,PaddingProps,Breakpoint,BREAKPOINTS,LayoutAttributes,VisibilityPropsandGridResponsive. - New
IconTile: a Facet icon on a tinted square or circle, for features, settings sections, stats and list items. It has three sizes (the control heights, so it scales with the theme), the six tones,soft,solidandoutlinevariants, and is decorative unless it has alabel, which makes it an image with that name. - Ten new Facet icons (250 in all) and a Google Play brand mark (98 marks).
calendarDays,checkSquare(a checkbox with a large tick, Lucide's square-check-big),bookMarked,listTree,refreshCwandqrCodejoin their categories. A newfaithcategory holdsmosque,kaaba,crescent(a crescent with a star) andprayerBeads(tasbih).googleplaycomes from Simple Icons, unchanged. Each has search words inICON_KEYWORDS, andgridis now also found by "layout grid". - Layout primitives take what page layouts asked for.
Stack,Inline,Grid,GridItemandContaineracceptid,roleandaria-*in their types (they already passed them on) andhideBelow/hideAbove(a breakpoint of the window: hidden with CSS, no script).Gridcolumnstakes a track list for unequal columns ("2fr 1fr").GridItemtakesspanandorderper breakpoint ({ base: "full", md: 2 }), measured on the grid's width like its columns, for unequal columns that change with the width and for reordering.GridItemandStacktakestickyandstickyOffset. The breakpoint scale is now shared asBREAKPOINTS;GRID_BREAKPOINTSis the same object. - New
List: a realulorolwith drawn markers (disc,number,check,icon,tileornone), each item a label with an optional description. Numbered lists are ordered lists that count fromstartand suit how-to instructions;gap,divided,sizeandtoneadjust the look, and markers follow the reading direction. - New
OptionList(withOptionListItem,OptionListNoteandOptionListGroup): the drop-down list of options under a field, which Select, Combobox and TagInput now all draw with, so their lists are one component instead of three copies. Restyle it with--cy-option-list-{bg,shadow,max-height,max-width,active-bg}; Select's--cy-select-list-*and--cy-select-option-active-bgand Combobox's--cy-combobox-list-*and--cy-combobox-option-active-bgkeep working and set them. TagInput's suggestion list follows--cy-option-list-*(it used to read Combobox's tokens). The lists look as before; Select's list now also drops its fade under reduced motion and outlines the active option in forced-colors mode, as Combobox's did. The list classes changed (cy-select-list,cy-select-option…,cy-combobox-list,cy-combobox-option…are nowcy-option-list…). Buttontakessquare(as wide as it is tall, no padding, for one icon) and, withhref,idandlinkRef(the link element).IconButtonis asquareButton and takesflipRtl(the icon is mirrored in right-to-left text),iconColorandwrapperClassName(layout of the element around it).TooltiptakesclassNamefor its anchor andIcontakesflipRtl. IconButton no longer has a stylesheet:dist/styles/iconbutton.cssis gone (its rules are now Button'ssquare).- Printing: closed
AccordionandDisclosuresections print open, and everyTabPanelprints, in order (unselected panels render their content while the page is printed). The sharedusePrinting()hook is insrc/foundations/printing.ts. - New
QRCode: a QR code drawn as one crisp SVG path from Cynosure's own encoder (versions 1 to 40, levels L, M, Q and H, numeric, alphanumeric or UTF-8 text).sizetakessm,md,lgor a number of pixels,quietZonekeeps the light margin scanners need, andiconputs a logo in the middle and raises the level to H. It stays dark on light in dark mode, because many phones cannot scan an inverted code.qrMatrix,qrPathandqrToSvgexport the encoder for pages without React. - New
Section: a full-width band of a page with a background (variant:plain,surface,soft,accent), padding above and below from a space token, and its content in a Container (size, orcontained={false}).as,id,aria-labelledby,aria-labelandroleare set on the root, so a Section can be a named region and an in-page link target. - New
SiteFooter: the bottom of a website. A brand and tagline, link columns as data ({ title, links }, in one navigation landmark, each list named by its heading), and a bottom row withlegalandsocialslots. Columns wrap and the bottom row stacks on narrow screens, and every link has a 44px hit area on touch screens. - New
SiteHeader: the top bar of a website that scrolls as a document, whereAppShell(an app frame) does not fit. It has a brand linked home, the site navigation as data ({ label, href, current? }, the current one markedaria-current="page"), anactionsslot, a SkipLink before it, and it sticks to the top over a blurred view of the page (sticky, default true). BelowcollapseBelow(the header's own width, CSS only) the links move into a menu Sheet opened from a button; Escape closes it and focus returns to the button. SkipLinkis shown as an accent outlineButtonlink, hidden by the shared.cy-visually-hiddenuntil it has focus (still CSS only, so it works without JavaScript), and takescontainedto show at the corner of the nearest positioned ancestor (AppShell sets it). Restyle the shown link through the--cy-button-*tokens; SkipLink's own tokens areoffsetandradius.- New
SkipLink: "Skip to content", hidden until focused and then shown at the window's top-start corner. It is a plain link that works without JavaScript; with it, focus moves to the target (giventabindex="-1"when it needs one) without changing the URL, also inside shadow roots.AppShellandSiteHeadernow both use it, so the library has one skip link. - Smaller additions:
Buttoncolor(any CSS colour, overridingtone; the text on a solid fill is white or black, whichever reads);Linkunderline(always,hoverornone) andaria-current;ThemeTogglestorageKey;PageHeadertitleSizeandHeadingsize="3xl"(1.4 times 2xl, between PageHeader's md and lg);EmptyStateheadingLevel={1}for a page that is the empty state;Cardaria-current, shown with the accent border (--cy-card-current-border). Stacktakesdirection("column", the default, or"row"), and itsalignaccepts"baseline".Inlineis now a Stack in a row: it renders Stack withdirection="row"and keeps its own centring andwrapin the newstyles/inline.css(Inline's rules used to live instyles/stack.css). Load both stylesheets for Inline when you import them one by one. Inline's props and look are unchanged.- New
Stat: a KPI tile on a Card with a label, a value in tabular figures, an optional unit, description and icon, and adeltathat draws an arrow and the change in a tone and is spoken as "up 12%" (the direction comes from the sign or is given, and the words translate throughlabels). The label and figures are a description list, so they are read together.hrefmakes the whole tile one link;as="li"keeps a linked stat inside its list item. Stepsworks as a numbered how-to:currentis optional. Without it every step is numbered alike, nothing is marked current, no status is read and the list is named "Steps" (labels.liststill overrides it). A step's ownstatusstill shows.- Every component now says which stylesheets it needs.
@arshad-shah/cynosure/styles/manifest.jsonmaps each component to its files, base first, worked out from the source imports at build time (Inline →stack.css, Sheet →dialog.css, Accordion →disclosure.css, IconButton →tooltip.css, Button →loading-dots.css). The Vite and Astro adapters takestyles: "imports"to add exactly those stylesheets for the components each module imports..cy-visually-hiddenmoved fromlink.csstobase.css, so Steps, Tabs, Calendar, Stat and the others no longer needlink.css. ColorField now liststext-field.cssand TagInputcombobox.css, whose classes they use. - New
VisuallyHidden: content for screen readers only (asspan,div,por a heading), with afocusablevariant that is shown while something inside it has focus. The shared.cy-visually-hiddenrule it uses moved fromlink.csstobase.css, so it no longer depends on loading Link's stylesheet.
Patch ChangesLink to "Patch Changes"
Buttonno longer cuts off the tails of g, y and p in fonts with deep descenders (Plus Jakarta Sans): the label's box holds the font's whole height. The button's height is unchanged.ChoiceBase's stylesheet is nowstyles/choice-base.css(it wasstyles/choice.css, which is now the Choice row's), and its class iscy-choice-base(wascy-choice-input). Update a one-by-one stylesheet import ofchoice.cssfor ChoiceBase tochoice-base.css.DeviceFramefixes from a product page. On a dark page the phone and tablet bezel no longer disappears: it is a dark grey lifted off the page with a lighter rim (3:1 against the page), and still the ink on a light page. Live content on a phone now starts clear of the camera island (below it upright, beside it turned) instead of running under it; a screenshot, video or canvas still fills the screen. The newsafeAreaprop decides for any content.- KeyValueEditor and Checklist no longer reach into the Checkbox and TextField they render: KeyValueEditor lays out its checkbox through a class of its own and hides its file picker with the shared
cy-visually-hiddenclass, Checklist finds its focus targets through its own classes, and TagInput's announcer usescy-visually-hidden. HiddenValuehides its input with the shared.cy-visually-hiddenclass fromstyles/base.css, sostyles/hidden-value.cssis gone; drop it from one-by-one stylesheet imports. Behaviour is unchanged.AvatarandImageshow their picture in server-rendered HTML that is never hydrated (an Astro page without a client directive). Before any script runs the picture shows as the browser has it; on mount an unfinished picture switches to the fallback or placeholder before the first paint, as before.Image'sdata-statehas a new first value,pending.- Each component styles only its own classes, with no change in look: Pagination, Calendar, CopyButton, QRCode, Dialog, Notice, SideNav, SortableList, Toast and FloatingToolbar place, mirror and colour the Buttons, IconButtons, Icons, Tooltips and Toolbars they render through those components' props and tokens and their own layout classes; Sheet draws its docked panel on its own classes (
.cy-sheet,.cy-sheet-panel,.cy-sheet-actions) over the Dialog's frame. Announcer, CopyButton, Field, FileDrop, SideNav's collapsed rail, SortableList, Steps and Table hide text with the shared.cy-visually-hidden. SiteHeader's brand and navigation links areLinks, drawn as before by the span inside each. SegmentedControl's internal classes follow its name:cy-segmentediscy-segmented-control,cy-segmentiscy-segmented-control-segmentandcy-segment-labeliscy-segmented-control-label. Restyle through the--cy-segmented-control-*tokens; the look is unchanged.- A product palette now reaches everything worked out from it.
createTheme({ palettes })applies each palette's colours first and derives from them:accentSoftis mixed with the palette's own surface (it used Cynosure's), an accent given for one palette re-derives that palette's hover, active, soft, text and on-accent colours, and the high-contrast palettes (prefers-contrast: more,contrast="more") start from the product's light and dark palettes instead of falling back to Cynosure's. Colours a patch sets explicitly still win.
Minor ChangesLink to "Minor Changes"
AppShellnow shares its narrow state, so products no longer repeat the breakpoint.useAppShell()returns{ narrow }to anything rendered inside the shell (it isfalseon the server, on the first client render and outside a shell), and the newonNarrowChangeprop tells code outside the shell once per change.- New
AspectRatiocomponent: a box that keeps a width-to-height ratio at any width, for a video, a map, a chart or an embed.ratiotakes a number or a CSS ratio such as"4/3"(default16 / 9) and the height comes from CSS, so nothing is measured. Its children are stretched to the box, images and videos cover it, andradiusrounds and clips the corners from the radius tokens. New
Avatar: a person, team or thing as a picture, falling back to initials worked out from the name (emoji and non-Latin names included) and then to an icon. It has five sizes, circle and square shapes, tones (withtone="auto"picking one from the name), a status dot that is spoken with the name, anddecorativefor when the name is written beside it.New
AvatarGroup: overlaps several avatars along the reading direction, gives them one size and sums up the rest as a "+3" avatar that names the hidden people.- New
Breadcrumbscomponent: the trail from the top of a site or app to the current page, as a "Breadcrumb" navigation landmark with an ordered list. Passitems(label,href,onClick,icon); every item but the last is a link or a button, and the last is the current page (aria-current="page").maxItemscollapses the middle of a long trail into a menu, long names are cut with an ellipsis and the trail wraps in narrow containers. - Button and IconButton now set
aria-currenton the element in both forms, so a link-form button (href) can say it is the current page; a link-form button also takesstylenow. Pagination withgetHrefrenders the current page as a link witharia-current="page"instead of a button. Pagination'sonChangegets the click event as a second argument,(page, event), so a client-side router can callevent.preventDefault()on a link press and navigate itself. CalendargainsmarkDay, which marks a day without drawing it: atonetints the cell,dotsputs up to three coloured dots under the number, andlabelis added to the day's accessible name. The day state passed torenderDayandmarkDayhas aweekendflag that follows the locale. WithrenderDay, a day's height is its own token,--cy-calendar-content-size, and defaults to 66px on touch screens (it was 110px), so a six-week month fits a phone while every day stays a 44px target.Calendarcan have its month set from outside:month(controlled,YYYY-MMor any date of the month) anddefaultMonth, withonMonthChangereporting the month asked for.monthPickerturns the month heading into a button that opens a panel to jump to any month and year withinminandmax. The focused day now always stays inside the month shown.Cardtakesedge: a coloured edge on its inline-start side for a category or status. A tone name uses that tone's colour and any other string is a CSS colour. It follows the reading direction and the card's radius, works with every variant and on link and button cards, and its thickness is the new token--cy-card-edge-width. Pair it with text or a Badge, since colour alone carries no meaning for many people.Checkboxhas a checklist style: withstrikeWhenChecked, a checked box strikes through and mutes its label and mutes the description, so a finished task reads as done. The muted label keeps 4.5:1 contrast in light and dark, the strike stays in forced colours, and the new tokens--cy-checkbox-done-colorand--cy-checkbox-strike-widthadjust it. Switch and RadioGroup are unchanged.- New
Checklist: a named list of rows people add, tick, rename in place and remove, such as a task's subtasks. It works controlled (items) or uncontrolled (defaultItems), moves focus to a sensible control after every change, and hasmaxItems,reorderable(drag or keyboard, through SortableList),disabled,readOnly, two sizes and overridable labels. - New
Container: the page column. It centres its content, caps its width (size:sm640px,md768px,lg1024px by default,xl1280px, orfullfor no cap) and adds side gutters from a space token that never get narrower than a phone's safe area.paddingBlock,centerandas(div,main,section,article) cover the rest, and the widths are tokens. - The default changed:
DialogandSheetno longer keep their content mounted while closed; pass the newkeepMountedprop to keep the old behaviour. The content now mounts when the dialog opens and unmounts once its closing transition has ended (at once under reduced motion), so every opening starts with fresh state, a closed dialog renders an empty<dialog>element (on the server too), and tests can no longer find the content of a closed dialog. UsekeepMountedfor content whose state must survive closing or that is expensive to rebuild. This also applies to what is built on them:ProgressOverlay, and the navigation drawer ofAppShell. - New
Divider: a line that separates content. It is horizontal by default and fills its container;orientation="vertical"stretches it to the height of a row such as an Inline or a footer of links. It can carry a short label in the line (labelAlignplaces it), bedashed, take astrongtone and add its ownspacing. It is decorative by default;decorative={false}makes it arole="separator"named by its label. Gridcolumnsalso takes a count per breakpoint,columns={{ base: 1, sm: 2, lg: 4 }}. The breakpoints (sm30rem,md45rem,lg60rem,xl75rem, exported asGRID_BREAKPOINTS) are widths of the grid itself, not the window, so it adapts inside panels, popups and shadow roots. It is CSS only, so server-rendered markup is right on the first paint, andGridItemspans are clamped to the columns in effect at each width.- New
Headingcomponent: a section or page heading whoselevel(theh1toh6element) is separate from itssize(xsto2xl, plusdisplayfor a hero title), so the page outline stays correct whatever the heading looks like. It hastone,weight,alignandtruncate(one line or a number of lines), uses the display font with a tight line height and balanced wrapping, and has no margins. Each size has a token (--cy-heading-size-*) so a product can retune its heading scale. - Icons:
ICON_KEYWORDS(from@arshad-shah/cynosure/icons) maps each Facet icon's name to other words it is found by (trash: delete, remove, bin), for galleries and icon pickers. The words are a separate export, so importing an icon does not bring them along. - New
Imagecomponent: a picture that never shifts the layout.ratioreserves its space with AspectRatio (orwidthandheightdo), a Skeleton shows while it loads, and a quiet box with the image icon and thealttext takes its place when it fails;placeholderandfallbackreplace either. It also hasfit,position,radius, lazy loading by default, acaptionthat makes a namedfigure, and it notices a picture that had already loaded before React attached. Stack,InlineandGridtakepadding,paddingInlineandpaddingBlock: space tokens that pad the layout's own box, so a wrapper element is no longer needed for it. The specific prop wins overpadding, and a nested layout never inherits its parent's padding. Layouts that do not use the new props are unchanged.- Icons in options can be coloured.
SelectandComboboxoptions andMenuandContextMenuitems takeiconToneandiconColor, with the meaning of Icon'stoneandcolor. Select keeps the colour in its trigger once the option is chosen. Disabled options show the icon muted, and icons without the new fields look as before. - New
PageHeadercomponent: the top of a page or a major section, withbreadcrumbs, aneyebrow, thetitle(the header's one heading, atheadingLevel1 to 3), a leaddescription, ametarow,actionsand content below. It is built fromHeadingandText. The actions sit at the inline end and move below the text when the header's own width is under 36rem;size="lg"withalign="center"makes a landing page hero, anddividerdraws a bottom border. New component: Pagination, for moving between the pages of a list or table. It is a navigation landmark of Buttons with previous and next, numbered pages with ellipses, optional first and last controls, a compact "Page 3 of 10" variant for phones, and real links through
getHref.The helpers
pageCount(total, pageSize)andpageRange(page, count, siblings, boundaries)are exported with it.SideNavtakes one primary action among its items:{ kind: "action", id, label, icon, onClick }(typeNavAction). Inlayout="bar"it is a round accent button raised above the tab bar, placed where it sits initems; in the column it is a full-width accent button and in the rail an icon button. It is a button, never the current page, and does not callonNavigate. New tokens:--cy-side-nav-action-sizeand--cy-side-nav-action-lift.- New
Textcomponent: one piece of text in the type scale, for labels, captions, lead paragraphs, helper lines and values. It has presets (body,lead,caption,eyebrow,code),size,weight,toneandalignprops that map to the font and colour tokens, one-line or multi-linetruncate, andnumericfor tabular figures. It renders aspanby default or the element given inas, adds no margins, and is not changed by host CSS. TimeField can have a picker, and shows the time the way the person's region writes it.
pickeradds a clock button at the end of the field that opens columns rolling under one lens, with a strip of the whole day to jump along (dayTrack={false}hides it) and the hours outsideminandmaxhatched. It is off by default: fields that do not ask for it look as they did.hourCycleis"auto"by default:9:30 PMwhere AM and PM are used,21:30elsewhere, from the browser or fromlocale.hourCycle={24}keeps the form every TimeField had before;hourCycle={12}fixes the 12-hour form and adds an AM/PM switch to the picker. The value, the limits and what a form receives stay 24-hourHH:MM.- New labels (
open,hours,minutes,seconds,am,pm,period,dayTrack,now) and tokens (--cy-time-field-row-height,-column-width,-lens-background,-night,-day).
Toastertakesoffset, to keep toasts clear of fixed parts of an app such as a bottom tab bar on a phone. One value (a space step like"8"or a CSS length like"56px") moves them in from the top or bottom edge they sit on;{ block, inline }also sets the distance from the side. The same distances are the new tokens--cy-toast-offset-blockand--cy-toast-offset-inline, so an app can set them in CSS, for example only below a breakpoint. The toast region now also stays inside the device's safe area (env(safe-area-inset-*)) on every side.
Patch ChangesLink to "Patch Changes"
- Theming:
createTheme({ components })overrides for components with multi-word names (sideNav,timeField,segmentedControl…) are now written as the custom properties their styles read (--cy-side-nav-…). Before, they were written with the camelCase name (--cy-sideNav-…) and had no effect.
Minor ChangesLink to "Minor Changes"
- SegmentedControl: two controls given the same
nameno longer share one selection. The segments are grouped under a name of the control's own (with ids of its own), andnamesubmits the value through a hidden field, so choosing in one control never clears another and a label never points at another control's segment. A form's reset still restores the default. The small size is now exactly as tall as other small controls (26px, was 28px); its segments keep a 24px hit area. In dark mode the active segment is now a visible pill: it used the raised surface, which on dark is as dark as the track, so only the text colour showed which segment was active.
Patch ChangesLink to "Patch Changes"
- AppShell: text hidden for screen readers (a Table's hidden caption, Kbd and Calendar labels) inside the content or the navigation now scrolls with that area. Before, it escaped the scrolling area and stretched the shell's container, so a full-height shell showed a second, page-level scrollbar beside the content's own.
Minor ChangesLink to "Minor Changes"
- AppShell:
tabsshows a bottom tab bar on narrow screens. SideNav:layout="bar"draws its top-level items as that bar. - New: Calendar (the APG date grid, with
renderDayfor a month of plans), DateField (typed, stepped or picked) and TimeField (24-hour, read loosely). Dates areYYYY-MM-DDstrings; the date and time helpers are exported. - Tabs: document tabs.
TabtakesonClose(a close button, and Delete on the focused tab);TabListtakesonReorder(drag, or Ctrl/Cmd+Shift with the arrows).Tabstakeslabels. - Code colours as tokens (
--cy-color-syntax-*, keywords from the accent, ≥ 4.5:1 everywhere and 7:1 in high contrast), an opt-instyles/syntax.cssfor CodeMirror 6, Prism, highlight.js and Shiki's css-variables theme, andmonacoTheme,textMateThemeandeditorColorsfor editors that take a theme object. No editor dependency. - FileDrop:
target="window"takes files dropped anywhere in the window, with a full-page overlay while they are dragged in. - First release of
@arshad-shah/cynosure: one small, themeable, accessible React UI package (tokens, foundations, controls, structure, overlays and feedback components, Facet icons and brand marks), replacing the old@arshad-shah/cynosure-*packages. - Icons and tokens for every product: 56 new Facet icons (with a new maths category), 97 official brand marks in
@arshad-shah/cynosure/brands(drawn exactly as their owners publish them,color="brand"for their own colour),iconToSvgfor pages without React, and a React-free@arshad-shah/cynosure/tokensentry (createTheme,themeToCss,themeInitScript). - KeyValueEditor:
allowFileslets a row's value be a file, for multipart form fields. - PanZoomCanvas:
renderScreendraws a layer in screen pixels under the content, for engines that paint into a<canvas>.CanvasScreenis exported. - What the product migrations asked for, as props on existing components: Badge
coloranddot(steady or "live"), ProgressBarsegments,offsetandmarker, Buttonhref,confirmandkeepFocus, TextFieldrevealable, ThemeTogglevariant="cycle", Cardactions, CodeBlockactiveLines, Select option groups, TreeonActionand revealing an outside selection, and theuseDraganduseControllableStatehooks. - SelectionFrame:
drawdraws a new box by dragging on the layer (region select), andboxmay benullbefore one exists. Enter places a box for keyboard users. - Slider: any number of thumbs (
valuemay hold more than two numbers), andtrackContentfor a custom-drawn track.SliderValuewidens tonumber | number[]. - SplitPane
unit="px"for fixed-size panes, Popoveranchor(a point or any element, withtriggeroptional), and NumberFieldscrub(drag the label to change the value). - VirtualList:
wrap(measured rows of any height),follow(stays at the end as rows arrive),isHeader(pinned section headings) andhandle(scrollToIndex,scrollToEnd).VirtualListHandleis exported, andmoveItemsnow lives in a shared foundation.