Releases
Changelog
One post per release of @codeatlasdev/ui, newest first. Follow it as an Atom feed (each entry carries the full post) or JSON.
Interfaces with soul: Theme Studio, a UI index and a faster gate
- Theme Studio
- The UI index and gallery viewer
- Design style pages
130 changes57 features62 fixes
@codeatlasdev/ui 4.5.0
- i18n: the message catalog is split per group and each component bun...
1 changesetsrelease notes
@codeatlasdev/ui 4.4.0
- NavItem in the AppShell panel gets color (a decorative dot), l...
- AppShellLayout takes panelWidth (px, held between 160 and 480,...
- Combobox: multiple turns it into a searchable multi-select (va...
27 changesetsrelease notes
@codeatlasdev/ui 4.3.0
- Demo sidecars accept names (motion vocabulary) and motion (exac...
- MotionStage and the MotionCtx type: the frame of a motion demo.
2 changesetsrelease notes
@codeatlasdev/ui 4.2.0
- AI feedback loop.
- Avatar renders the photo as a real <img> (no detached Image() p...
- Card has a padding prop ('none' | 'sm' | 'md' | 'lg', any CSS...
22 changesetsrelease notes
@codeatlasdev/ui 4.1.0
- Numbered demo catalog (ca-prompt): every docs demo has a stable ...
- Checkbox, Switch and RadioGroup: on touch screens (pointer: coarse...
2 changesetsrelease notes
@codeatlasdev/ui 4.0.0
- AppShellLayout renders its content region as <main id={mainId} t...
- nativeFeel() now has one opt-out class, .ca-selectable, for bot...
- caBrandPlugin writes src/generated/brand.ts as soon as the Vite...
21 changesetsrelease notes
@codeatlasdev/ui 3.1.0
- .ca-prose styles release lists, so a changelog page needs no cust...
- Add SwatchGroup: a single-choice color swatch picker (role="radi...
- generateBrand and the brand Vite plugin no longer load every syst...
5 changesetsrelease notes
@codeatlasdev/ui 3.0.0
- Sober default theme, Liquid Glass is now opt-in (#111).
- AppShellLayout: the shell keeps its state across page loads (#109).
- Brand assets from one mark.
5 changesetsrelease notes
@codeatlasdev/ui 2.3.2
- DocsShell: the sidebar keeps its place between pages.
1 changesetsrelease notes
@codeatlasdev/ui 2.3.1
- Theme islands: a data-theme-scope inside a data-preset island n...
1 changesetsrelease notes
@codeatlasdev/ui 2.3.0
- The generated API reference now documents every non-component expor...
- New .ca-sr-only utility (visually hidden, still in the accessibil...
- Add the @codeatlasdev/ui/testing subpath: expectNoA11yViolations...
13 changesetsrelease notes
@codeatlasdev/ui 2.2.0
- Accessible tokens and states, solved per preset and scheme by the g...
- Component CSS now lives in @layer ca.components, declared last in...
- i18n: open locales and request-scoped language (fixes #46).
5 changesetsrelease notes
@codeatlasdev/ui 2.1.1
- Steps: a leading <strong>/<b> step title is a block, so its l...
1 changesetsrelease notes
@codeatlasdev/ui 2.1.0
- Theme: port a palette by role and pick the page paper, without raw...
1 changesetsrelease notes
@codeatlasdev/ui 1.2.0
- Design tokens v2: every visual dimension is a theme knob, so one co...
- RadioGroup: variant="cards" (selectable cards with label and des...
- Remove the legacy theme editor generator: editorTokensToCSS, edi...
4 changesetsrelease notes
@codeatlasdev/ui 1.1.0
- Accessibility foundations.
- Developer and agent tooling.
- Performance foundations.
7 changesetsrelease notes
@codeatlasdev/ui 1.0.0
- 1.0.0: stable API. From here on, breaking changes to props, exp...
1 changesetsrelease notes
@codeatlasdev/ui 0.7.1
0 changesetsrelease notes
@codeatlasdev/ui 0.7.0
0 changesetsrelease notes
@codeatlasdev/ui 0.6.0
0 changesetsrelease notes
@codeatlasdev/ui 0.5.2
0 changesetsrelease notes
@codeatlasdev/ui 0.5.1
0 changesetsrelease notes
@codeatlasdev/ui 0.5.0
0 changesetsrelease notes
@codeatlasdev/ui 0.4.4
0 changesetsrelease notes
@codeatlasdev/ui 0.4.3
0 changesetsrelease notes
@codeatlasdev/ui 0.4.2
0 changesetsrelease notes
@codeatlasdev/ui 0.4.1
0 changesetsrelease notes
@codeatlasdev/ui 0.4.0
0 changesetsrelease notes
@codeatlasdev/ui 0.3.0
0 changesetsrelease notes
@codeatlasdev/ui 0.2.2
0 changesetsrelease notes
@codeatlasdev/ui 0.2.1
0 changesetsrelease notes
@codeatlasdev/ui 0.2.0
0 changesetsrelease notes
All published versions
The list GitHub Releases shows right now, with every version's notes and anchors.
4.6.0 latest
Minor Changes
0219812: AI agents get one canonical entry point.
llms.txtopens with a recipe for every screen type (app shell, list, form, settings, auth, dashboard, landing, theming), then decision tables and a "common workarounds" page;get_guideand the new per-page files/llms/<slug>.txtcarry the complete demo source of each page, whilellms-full.txtstays under its old size.ca-ui-mcpaddsget_recipeand reports deprecated components (Row,GlassCard,GlassSurface) with their replacement.initwrites a marked, idempotent block into the app'sAGENTS.md. The agent skill no longer teaches the removedcols/pixel-gaplayout API or deprecated components.1b1b993:
AspectRatio(#446, part of #432): a box that holdsratiofor a video, an embed or any child with no size of its own; a directimg,video,iframe,canvasorsvgchild fills it.Imagegainsratio(crop to a width / height box, filling the container) andfit(coverorcontain). Replaces the RadixAspectRatiorow of the React equivalence table.23fa9ee: Auth building blocks (#379):
Turnstilein@codeatlasdev/ui/forms(Cloudflare captcha, explicit render, loads the vendor script on demand with no dependency and no server access, follows the colour scheme and the locale,bind:handlewithgetToken()/reset(), removed on unmount, silent no-op without asiteKey),PasswordStrength(3-step meter with a polite text label, purepasswordScore()and ascore/labelsoverride),SplashScreen(role="status",ready+minDuration, skipped under reduced motion) andAvatardecorative(no accessible name next to a visible name). New i18n groupspasswordStrength,splashScreenandturnstile(en, pt-BR).00757fe:
AuthLayout:panelTone(auto|brand|flat) and the--ca-auth-panel-bg/--ca-auth-panel-fgtokens. A flat panel is a solid surface with a hairline, themed text and no blobs; the defaultautopicks it under theflatandoutlinedsurface styles, so themes that forbid gradients no longer override.ca-auth__paneland.ca-auth__blobsfrom app CSS (#409). The panel slot's--panel-*variables are now CSS (derived from--ca-auth-panel-fg) instead of an inline style.78ddb7f:
AuthLayout:panelSide(left/right),panelBleed(full-bleed, sticky panel with no padding, logo or blobs, and a hairline divider),panelHidden(md900px orlg1200px; the panel snippet is unmounted while hidden, so media inside is never fetched on mobile), aheadersnippet (sticky bar over the form column that replaces the mobile brand strip) andalign="start".AuthCardgetsvariant="plain"(display-size title, no card width cap). New template: Waitlist with map (#347).b192b0a:
Badgerenders an inline status pill:<Badge tone="danger" subtle dot size="md">Overdue</Badge>(tonesbrand,success,danger,warning,info,neutral;subtleis a tinted background whose label clears 4.5:1 in light and dark).ButtongainstrailingHint(aKbdintrailingis hidden belowmdand on touch with no reserved gap) andalign="between"(label at the start,trailingat the end; the spinner keeps its place). Defaults are unchanged.acdf4f3:
ButtonGroup(#448, part of #432): attachesButton,IconButtonand aDropdownMenutrigger into onerole="group"control, horizontal or vertical (orientation), with square inner corners, a single-line seam and the hovered or keyboard-focused member raised over it. Replaces the shadcn Button Group row in the React equivalents table.8c31f6a:
ButtonandIconButtongettone="neutral"(#357):outlinedandghostbuttons drawn with a neutral label, border and hover/pressed fills instead of the brand colour, for secondary actions beside a brandfilledone.IconButtonalso gets theoutlinedvariant. Label and border contrast is checked for every preset intests/contrast.test.ts.61912be:
CarouselandCarouselItem(#445, part of #432): native scroll-snap slides that follow the WAI-ARIA carousel pattern (labelled region, slides named "n of m", off-screen slidesinert, labelled dot buttons witharia-current, arrows / Home / End on the focused carousel).perView,loop,bind:index,onchange;autoplay(ms) is opt in, adds a Pause button, stops on focus or hover, is a polite live region only when stopped, and never runs underprefers-reduced-motion. No carousel engine dependency. Replaces shadcnCarousel. New i18n groupcarousel(en, pt-BR).82edb9d:
ca-ui-mcpgainsget_release(version): a release as a post for agents, with its highlights, the numbers and the conditions each was measured under, the behaviour changes to check after upgrading, the upgrade command and every merged change grouped by area with its pull request and issue. Without a version it returns the digest of every release. It readsdist/releases.json, built from the release posts in the docs site.7b30608: New
ChipGroup: single choice among a few short options as chips that wrap (#355). A realradiogroupof native radios (roving tab stop, arrows/Home/End/Space/Enter, disabled items skipped), three columns belowsmand free wrap fromsm, a check icon on the selected chip,errorasrole="alert"witharia-invalid/aria-describedbyon the group,name/requiredfor native forms, 44px chips. Use it instead ofRadioGroup(too tall) orSegmentedControl(never wraps) for options like team size.689d2c5: New token
--ca-code-line-height(20px): an integer px line height for code surfaces.CodeSnippet,.ca-code preand a bare Markdownpreinside.ca-proseuse it instead of the unitless 1.6 / 1.7, which rounded to a different pixel height in a text layer and in an overlay (a caret and an active-line highlight drifting apart). Thecodeinside aCodeSnippetalso pins--ca-font-mononow: it used the UA's baremonospace, resolved per lookup. The docs gain "Code surfaces: integer line-height, one font stack" for apps that build an editor.The stylesheet also drops
--ca-action-focus, a token no component, doc or template ever read, to stay inside its size budget.caf82ad: Component tokens for
Button,IconButtonandKbd. Weight, tracking, line-height, gap, per-size padding, height and font size, ghost border width, disabled background and the touch minimum are--ca-btn-*,--ca-icon-btn-*and--ca-kbd-*custom properties; set them on:root, on a wrapper or through the newThemeOptions.components(button,iconButton,kbd, merged bymergeTheme). Unset, every component draws exactly what it drew in 4.5.0. One naming convention for all components (--ca-<component>-<property>,--ca-<component>-<size>-<property>) is documented in the theming guide. No more selectors against.ca-btn--lg,.ca-icon-btnor.ca-kbd.3b76318:
ca-ui-mcpgainslist_conceptsandget_concept: the Concepts pages (viewport-complete UX, motion that respects people, targets and focus, appeal is not usability) with every claim's strength label, scope, sources, limits and last-reviewed date. The llms-full concept digest now carries the contract and the prompt only, so the file stays under its size cap.e125cc8:
ContextMenu(#455): wraps a region and opens aDropdownMenuat the pointer on right-click (a second one moves it), at the finger on a touch long-press (longPressDelay, default 500 ms, cancelled by lifting early or dragging) and from the keyboard with Shift+F10 or the ContextMenu key. Themenusnippet takes the same items asDropdownMenu(items, checkbox and radio items, submenus, labels, separators). Text fields keep the browser's menu;disabledkeeps it everywhere;bind:open,onOpenChange,focusable,menuClass.c19ba2b: Density and appearance tokens for
Avatar,TextArea,TextField,Card,SegmentedControlandEmptyState, on the same--ca-<component>-<property>convention as the Button tokens, also settable throughThemeOptions.components.Avatargetstone="neutral"(surface background, secondary text, hairline ring) and--ca-avatar-fallback-{bg,color,size,weight}with per-size--ca-avatar-<size>-fallback-*.TextFieldandTextArearead--ca-field-gapand--ca-field-label-leading(or their own--ca-tf-*and--ca-ta-*);--ca-ta-min-heightis the height of the visible box, border included.Cardreads--ca-card-{bg,radius,border,shadow}.SegmentedControlreads--ca-seg-item-{padding-x,font-size,line-height}(and per size).EmptyStategetsbare,actionsFilland--ca-empty-{padding,title-size,title-weight}. Unset, every component draws what it drew in 4.5.0.ab42958:
ca-ui-mcpgainssearch_terms,get_termandget_reference: the 269-term design atlas and the verified sources behind it, offline fromdist/atlas.json. Scores are labelled heuristic, every term carries its limits and review date, and a reference returns what it does not support and the wording that was read.73f38cd: Design style pages (#583):
/styles/<id>/forswiss,editorial,brutalism,sketchbook,glassmorphismandterminal, each rendered in its own style, with the origin, the traits (type, colour, shape, depth, motion), do and don't, accessibility pitfalls with their sources, before and after on real components, thedesignStyle()recipe and a prompt. Theca-ui-mcpserver gainsget_style(a style as markdown, or the digest of all of them) andllms.txtlinks the digest at/llms/styles.txt.designDefaults()is exported: the stylesheet default of every knob a theme can write, so a scoped preview can reset what an outer theme set.300ac7c:
Dialogsplit is configurable:splitSize={{ width, height }}(any CSS length),splitColumns="2fr 3fr"andfullscreenBelow="sm" | "md"(any size: exactly the viewport under the breakpoint, square corners, safe-area padding kept). They set the tokens--ca-dialog-split-width,--ca-dialog-split-heightand--ca-dialog-split-columns. Defaults are unchanged. The insertion-line-free column drop requested in the same issue already ships assortable({ drop: 'container' }); it now has a test for the announced target container on keyboard and touch.ca5cc0e: Display text role (#350). New
Headingcomponent (level,size="display" | "display-2" | "display-3" | "h1".."h6") bound to--ca-font-display(the body font untilfonts.displayis set), the new--ca-display-tracking/--ca-display-leadingtokens and the fluid--ca-font-size-display-*sizes.AuthCard,PageShell,EmptyStateandSiteHeadertake adisplayprop.defineTheme({ typography: { displayTracking, displayLeading } })sets the tokens.e5c7c0f:
DocsHeaderis the top bar ofDocsShellas its own component, so the pages of a site that are not a docs frame (a landing page, a template gallery) render the same header as the docs: one height, container width, brand position, section tabs with an underline for the current one, search (⌘K), actions and theme toggle, and the same phone menu. The bar is now opaque (it was an 82% translucent fill with a blur), so content scrolled under it no longer shows through.4e4bd67:
DrawergetschromelessandfullBleedBelow(#362), mirroringDialog'schromeless.chromelessrenders no header, close button or footer actions and no body padding, so a detail view can bring its own header and scroll area; the panel is still named bytitle(screen readers only),aria-labeloraria-labelledby.fullBleedBelow="sm" | "md" | "lg"makes a left/right panel exactly the viewport width and--ca-vhheight below that breakpoint (no radius, border or page strip, safe-area padding kept)..ca-drawer__bodyis now a flex column withmin-height: 0, so a child can own the scroll.2cc7983: Editorial primitives (#353). New tokens
--ca-font-size-display-hero(fluid, 60-216px) and--ca-ease-out-expo/--ca-ease-out-quart/--ca-ease-in-out-quart(also in theeasingexport). NewHighlightandStrikeThroughcomponents withanimated, drawn in their final state underprefers-reduced-motion.Surfacetakesgrain(true | "animated"), a paper-noise overlay tuned by--ca-grain-opacity/--ca-grain-size. Fix:--ca-font-mono(andfontFamily.mono) now starts with the system monospace instead of JetBrains Mono, which the kit does not load; setfonts.monoto pick another.446c722:
HoverCard(#454): a rich preview that opens on pointer hover (afteropenDelay) and on keyboard focus (at once), stays open while the pointer crosses into it (closeDelay), closes on Escape and blur, and is reachable with Tab from the trigger. A hint layer: it never takes Escape or presses from the layer below. Touch never opens it, so a tap only runs the trigger's own action (usePopoverfor a tap-to-open preview).triggerChild/trigger,bind:open,onOpenChange,side,align,sideOffset.FloatingLayerforwardsonfocusout.c8e9492:
ca-ui-mcpsearches the gallery index.search_uitakes words plus facet filters (kind, subtype, style, motion, interaction, layout, colorMode, density, tech, cost, component),list_facetslists every value with its count for the current selection, andget_relatedreturns the entries related to a demo. They read the same published facets asapi/v1/index.json: a facet still under review is never searchable.a5bc3b7:
init --writenow produces a SvelteKit app that passesvite build,svelte-check --fail-on-warningsandcodeatlas-ui lintas written, on Kit 2 and Kit 3.- PWA and SEO are opt-in. The manifest route, service worker, offline page,
robots.txtandsitemap.xmlare written only withinit --with pwa(also combinable:--with lint,e2e,pwa). A plaininitwrites nomanifestlink in<Seo>(the old default failed the prerender with a 404 as soon as the manifest route was removed).--with pwacan be added later to an appinitalready set up. - Lint-clean output. The copy
initwrites (layout title, offline page, manifest name) lives insrc/lib/messages.ts(export const m), socodeatlas-ui lintno longer reportsi18n-literalon a freshly initialised app. - Kit 3
#lib/*map.initadds"imports": { "#lib/*": "./src/lib/*" }topackage.json(never replacing an existing map) and imports the theme and messages through it, instead of relative paths. - Missing config files are created:
vite.config.ts,src/app.html(without the inline-styleddisplay: contentswrapper) andtsconfig.jsoninstead ofskip ... (not found). Kit 2'stsconfig.jsongetsallowImportingTsExtensions,noEmitandskipLibCheck, which its generated base config lacks; an existing file only gains the options it does not set. @sveltejs/adapter-static: the adapter output folder (build, or thepagesoption) goes into the tsconfigexclude, sosvelte-checkno longer scans it, andsrc/routes/+layout.tsgetsexport const prerender = true;.initnotes when the adapter major does not pair with the Kit major (@sveltejs/adapter-static@4for Kit 3,@3for Kit 2).prerender.handleUnseenRoutes: "ignore"is written into thevite.config.tsthat--with pwacreates on Kit 3 (no page links to/robots.txtor/sitemap.xml), and noted otherwise.- Fixed the public-origin note. Kit 2 rejects
kit.paths.origin; the mechanism iskit.prerender.originthere andsveltekit({ paths: { origin } })on Kit 3. - A re-run no longer mistakes the theme import
initwrote intovite.config.tsfor an app-owned theme.
- PWA and SEO are opt-in. The manifest route, service worker, offline page,
a73cb68:
inlineonDialog,Sheet,Drawer,Popover,DropdownMenuandToasterrenders the overlay in place instead of the top layer:position: absoluteinside its container, never moved to<body>, noshowModal()/showPopover(), no scroll lock, no focus trap, no inert page, no global Escape or outside-press ownership (Escape closes it from inside). It resolves the tokens of its container, so it follows adata-presetordata-theme-scopeisland.openLayertakes the sameinlineoption. The Theme Studio overlays screen now shows the real components instead of static replicas.d101e43:
TextFieldaddons, the InputGroup (#447, part of #432):prefixandsuffixput text or aButton/IconButton/select inside the field border, flush with it and set apart by a divider. A string addon is read as part of the field's description and focuses the input on click; a snippet addon keeps its own name and Tab stop. The focus ring wraps the whole group;error,disabledandsize="sm"reach the addons;--ca-tf-addon-bgsets the fill..ca-tf__addonclasses documented indocs/css-api.md.1263da3: New
Kanbanboard (#361) on top ofuseDnd: columns with a fixed header (colour rail, title, count), independently scrolling card lists, a board that pans with the mouse and fades only where content is hidden, per-columnonloadmorewith skeleton cards and a "Load more (N)" fallback, a mobile tab strip (one column at a time) and a "Move to…" menu next to every card. Mouse, pen, touch and keyboard moves are announced. The engine gainssortable({ drop: "container" })(column highlight, no insertion line,to.indexalways 0) anddnd.scroller()(horizontal board auto-scroll, faster the closer the pointer is to the edge); Left/Right move a card to the same index of the neighbouring column, an empty column included.46129ad:
Kanbantakes asummarysnippet that renders per-column content (a money total, a weight) after the count while the colour rail, title and count stay; on the mobile tab layout it shows as a row above the active column's cards.headerstill replaces the whole title row. The kanban docs show a per-column money total and a board-height parent without:global.62a8d05:
KanbantakesheadingLevel(2–6, likeEmptyStateandFormSection) for its column titles (#501). The default level changes from<h3>to<h2>(inside aPageShell, the page's section level), so a board under the page<h1>no longer skips a level and passes axeheading-order. A board under an<h2>of its own passesheadingLevel={3}to keep the old markup. Only the tag changes, never the look; a customheadersnippet and the mobile tab layout (no column headings) are unaffected.f6085f9:
KpiCardtakes achartsnippet (aSparklineunder the number, next to the icon) and ahintline, so an icon + sparkline tile needs noCard+Stat+Sparklinecomposition. The chart is a snippet rather than a data prop to keep the/chartssubpath andechartsout of the core entry. New part:.ca-kpi__chart.25bd205: New
layersstore:layers.modalOpenis reactive and true while a modal layer (Dialog, AlertDialog, Drawer, Sheet) is open;layers.blocks(trigger)is the same but false for a trigger inside the topmost modal.Tooltip,HoverCardandNavigationMenuItemno longer open on pointer movement while a modal is open (unless their trigger is inside it), so a hover panel can no longer appear under a dialog. Gate your own hover or edge UI with the store.d55e2a5: New
LocaleSwitcher: a compact language selector for public headers. Options are data ({ value, label, short?, lang? }), each named in its own language with alangattribute per item;onselect(locale)may return a promise (the switcher is busy until it settles,valuechanges only on success,onerroron failure); the trigger shows the native name from 600px and a short code below (or always withcompact). Compose it in thetrailingslot ofSiteHeader, now documented as the end of the bar. New i18n grouplocaleSwitcher(en, pt-BR) (#349).c172f2c: New optional entry
@codeatlasdev/ui/mapfor map products.MapStagewrapsmaplibre-gl(default) ormapbox-gland keeps the basemap in step with the theme (styles: { light, dark }, swapped withsetStyleon the same map), draws deck.gllayersinterleaved throughMapboxOverlay, fits bounds withpadding, and turnsflyTointojumpTounderprefers-reduced-motion.MapControlsadds glass zoom, compass (and optional geolocate) buttons with 44px touch targets.tokenColor(token, alpha?),mapColors(),useMapTheme()andrgbaCss()resolveoklch()/color-mix()tokens to[r, g, b, a]through a canvas, cached by resolved value so a theme change needs no manual reset. All engines are optional peers loaded insideonMount, so the stage renders in SSR. Pin@deck.gl/*to~9.4.8607da1:
MenubarandMenubarMenu(#456): the WAI-ARIA menubar for application menus. One tab stop; Left/Right/Home/End move between bar items (disabled skipped, wraps); Enter, Space or Down open a menu on its first item, Up on its last; inside an open menu Left/Right switch to the adjacent menu, while a submenu keeps its own arrows; Escape returns focus to the bar item; hover switches menus only once one is open. Menu content is theDropdownMenuitems unchanged.DropdownMenugainsonmenukeydown,ontriggerkeydownandopenFocus.abb6269: Migration tooling per major (#382).
migrations/<major>.jsonis the machine-readable record of every breaking change from 1.0.0 to 4.0.0 (before, after, why, and whethermigratecovers it:yes,partial,no); it generatesMIGRATION.md,dist/migrations.jsonand a new MCP toolget_migration(from, to).codeatlas-ui migrate --from 2 --to 4 [--write] [--report json|md] [--strict]runs only the codemods of the majors in that range, lists what it could not rewrite with file:line (Gridcolstemplate strings,.ca-overlayselectors,setLocale, abits-uiimport, ...) and the changes left to review by hand, and exits 1 under--strict. It no longer answers a bare "Nothing to migrate": it names the codemods it checked. New codemodselectable-to-ca-selectable(4.0.0:class="selectable",class:selectable,.selectablein a style block). The dev warnings ofGlassCard,GlassSurface,RowandsetLocaleon the server cite their migration id. A major changeset must now carry aMigration: <id>line naming an existing entry, and a test fails otherwise.NavModuleisbasePath/itemsin every release from 1.0.0 on: thepath/sectionsshape in the old skill examples never shipped, so there is nothing to migrate.043c32e: Migration tooling for teams moving an existing app onto the library (#383). New commands of
codeatlas-ui:tokens import <css|json>turns the custom properties of an existing design system (hex,rgb(),hsl()oroklch(), with or without alpha, one rule per theme) into a typedtheme.tswith a preset per theme, composites every translucent token over its background and prints each token it flattened and each text or outline pair under 4.5:1 or 3:1 (--strictexits 1);doctorchecks peer versions, optional peers, theme wiring, viewport meta, a rootfont-size,TODO(lib#n)markers and.ca-*selectors, one line per finding with a fix hint, and--fixrenames the markersworkaroundsreads;lintis a UI guard that reads.svelte,.astroand.css(no hex, raw px, inline style,:global(.ca-*)outsideui-guard.config.json, literal text in templates);compare <urlA> <urlB>opens two pages in Chromium and prints the elements whose box, computed styles or pixels differ, sorted by deviation.init --with lint,e2ewrites the guard scripts and a Playwright config with a 390/768/1280 matrix and an axe spec.migraterenames the pre-1.0NavModulekeyspath/sectionstobasePath/items(registry entryv1-navmodule-basepath-items).@codeatlasdev/ui/testinggainswithTheme({ scheme, preset, style }),coarsePointer()andmeasure(el, props).330d761:
NavigationMenu,NavigationMenuItemandNavigationMenuLink(#457): disclosure navigation for marketing headers, notrole="menu". Anavwith a list of links andaria-expanded/aria-controlsbuttons that reveal a panel of links; one tab stop with Left/Right/Home/End across the top level, Enter/Space toggle, Down opens into the panel, Tab flows through the panel links and on, Escape returns focus to the button. Hover opens afteropenDelay, switches at once while one is open and closes aftercloseDelay; a click toggles (touch).currentsetsaria-current="page";descriptionadds a second line to a panel link.98a7396: PageFrame: page container for map-first apps.
modemap | split | page,side,widthsm | md | lg | xl, a non-modal bottom sheet below 900px withpeek | half | fulldetents (drag or keyboard),padded,headerBorderand fixedheader/toolbar/footerslots. NewstageInsetsstore (and--ca-stage-inset-*on<html>) publishes the rectangle the frame covers so the stage can keep pins and routes clear of it;mode="page"setsdata-ca-stage="hidden"on<html>(#344).cbc214c: PageShell: new
asidesnippet (companion column, sticky beside the content from a 60rem-wide shell, after it below) withasideLabel, andwidth="reading" | "wide"to cap the content column at 46rem. AppShellLayout: newcollapseShortPanelprop collapses the panel to the rail for modules with 2 destinations or fewer (#342).539c4be: Preset role colors take alpha, and a preset carries domain tokens. A role in
colors.<scheme>is any CSS color (oklch(1 0 0 / 0.62),rgb(),color-mix(…)) or{ color, alpha }, so a translucent text or border follows the surface behind it instead of being flattened to a hex; a value that is not one CSS color throws at build time, andcaThemePlugin/caUIwarn when a hand-settext,textSecondaryortextHintis under 4.5:1 after compositing its alpha.PresetDefinition.tokens({ status: { new: { light, dark } } }) emits--ca-token-status-newplus derived-subtleand-contrastper preset and scheme, with names typed through theCaTokenRegistryaugmentation.tokenRgba("status.new")andonTokensChange(fn)(@codeatlasdev/ui/tokens) read any token as[r, g, b, a]within 1/255 of the CSS value, translucent colors included; the map entry'stokenColornow shares that resolver.#rrggbbpresets behave as before.5eebf5e: Theme presets can own the primary-button and field roles:
colors.<scheme>.brandFill,brandFillHoverandbrandContrast(--ca-brand-fill,--ca-brand-fill-hover,--ca-brand-contrast) andsizes.inputFontSize(--ca-input-font-size, stillmax(16px, …)on touch and below 900px). They are emitted with the same selectors as the other preset roles, soButton variant="filled"inside[data-preset="x"]needs no app CSS.caThemePluginwarns whenbrandContrastonbrandFillis under 4.5:1 (caUIfor Astro does too) (#354).70cce85:
PreviewStage: a resizable device frame for showcasing a component or a whole page at 390, 768, 1280 or any width. A segmented width control and a keyboard-operable splitter set the frame width; a frame wider than the stage is scaled whole. Inline content is themed in its ownThemeIsland(scheme,preset);srcshows a same-origin page in an isolated iframe.c99fb14:
Progressgetssize="sm" | "md" | "lg"(4px, 6px, 8px; the default stays 8px) and honours a--ca-progress-heightcustom property for any other height (#358). Determinate and indeterminate bars both follow it and the ends stay pill-shaped.2ff23ff:
AppShellLayoutnavigation for apps with many modules (#346):railLabelscaptions under rail icons, arailTopslot for a search / ⌘K trigger,NavModule.badge(number or'dot') withbadgeLabelfolded into the button's accessible name, a bottom navigation that keepsbottomNavPrimarytabs and moves the rest into a "More" dialog by default (native<dialog>,bottomNavOverflowslot; opt-inmoreSheet={NavMoreSheet}swaps in a fullSheet), andfilterModulesfor role-based navigation (the active module is resolved on the filtered list).Railtakestopandlabels,BottomNavtakesprimaryandoverflow.043c32e: Migrating a React app is now data, not prose (#383).
equivalents/react-ui.jsonmaps every shadcn/ui component (66) to the Radix primitive, Base UI package and MUI component that mean the same thing, the@codeatlasdev/uitarget with its import path and prop renames, and, for the nine components the library lacks, the gap issue (#432) and a workaround. It is served by the new MCP toolget_equivalent("Dialog"), shipped asdist/equivalents.json, and generated into the migration guide, so the page cannot drift from the data.codeatlas-ui migrate-reactlists every shadcn, Radix, Base UI and MUI import of a.tsxapp with its replacement, and in an already-ported.sveltefile renamesclassName,onClick,htmlForand the shadcnButtonvariants, reporting with file:line the props it cannot rewrite safely (onChange,onCheckedChange,asChild, spreads). The guide gains a Tailwind map and the React patterns that change shape (context, data fetching, routing, forms, SSR global state, per-request i18n), each with a type-checked Svelte block.5b50d09: The reset layer (
@layer ca.reset) now gives every interactive shape a pointer cursor:button,a[href],label[for],summary,[role=button],[role=tab],[role=option]and[role=menuitem*].:disabledand[aria-disabled=true]getnot-allowed. A custom clickable tile no longer shows the arrow cursor; an app overrides the rule with an unlayeredcursordeclaration, no!important. The stylesheet drops the now redundantcursor: pointerof.glass-btn*and.ca-rail-btn, the unused.ca-bar__actionsrule and the unused--ca-grid-columnstoken to stay inside its 17250 B budget.5931a06:
ResizablePanels,ResizablePanelandResizableHandle(#444, part of #432): panels split by WAI-ARIA window splitters. Sizes are percentages that add up to 100; a handle trades only with its two neighbours and stops at each panel'sminSize/maxSize. Arrow keys move 1%, Shift 10%, Home and End go to the limits (Up and Down in adirection="vertical"group, mirrored in RTL).persistkeeps the layout inlocalStorageand ignores a stored layout that does not match the panels;onlayoutreports it. Replacesreact-resizable-panels/ shadcnResizable. New i18n groupresizablePanels(en, pt-BR).194723a:
AppShellLayoutstagesnippet (#343): a persistent background layer (map, canvas, video) rendered once behind the pages, so it keeps its state across route changes. The content region passes pointer events through (panels opt back in with.ca-stage-panel), the shell paints nothing behind it, andstageVisible={false}ordata-ca-stage="hidden"on<html>hides and pauses it without unmounting. The shell exposes--ca-shell-inset-top|right|bottom|left(rail and panel, mobile header, bottom navigation and safe area).b46a5f7:
SimpleTablegets a dense, spreadsheet-like mode (#360):density="compact"(rows up to 40px),frame={false},scroll="none"(the caller owns scrolling, so the sticky header sticks to your page or panel),stickyColumns={n}(pinned columns keep an opaque background in hover, selected and focus states) andheaderCase="sentence". NewTableRow(selected,onactivate: Tab, Enter/Space and click, nested controls never bubble),TableGroupRow(collapsible,aria-expanded, sticky label) andTableSortHeader(sort,onsort,aria-sort).b8edc32: New optional entry
@codeatlasdev/ui/sound: opt-in, synthesized interface sound with no audio files (under 4 KB gzip). One sharedAudioContextis created only inside the first pointer or key event after the user turns sound on with<SoundToggle />(aria-pressed, accessible name, optional volume slider); the choice persists inlocalStoragekeyca-soundand sound is off by default. Every cue is a short oscillator and noise recipe behind four profiles (system,glass,brutal,terminal) that follow the design style through--ca-sound-profile(written bydesignStyle()), tunable with--ca-sound-gain,--ca-sound-pitchand--ca-sound-brightness. Hover and slider tick cues are off underprefers-reduced-motion; sound is suspended while the tab is hidden or media plays, with one cue per name per 40 ms and at most four voices. Button, Switch, Toast (toast.*(msg, { sound })), Dialog, Tabs and Slider take asoundprop (boolean | CueName) that emits throughsoundBus, a no-op hook exported from the root, so core stays silent without the entry.use:sound(thesoundexport is also the action) plays a cue on any DOM event. Docs: Feedback > Sound, and the Sound principles guide.b476462: Official list of stable override hooks (#381). The manifest gets a
stylingsection per component (components.json): stableca-*parts, internal parts (ca-x-*classes and@keyframesnames, which can change in any release), component-scoped--ca-*variables and thedata-*attributes its CSS selects on. It is surfaced inAPI.md, indist/parts.jsonand in a new MCP toollist_parts;get_componentreturns it too.codeatlas-ui workaroundsnow also scans.css,.svelteand.astrofor.ca-*selectors that are not a stable part, and--strictexits 1 on them (aca-ui-workaround(#n)marker on the line or the one above excuses it). A test fails when a documented part is no longer applied by any markup; it found and removed the dead.ca-date__seprule.3b50a5c: New
Stat(#359): a card-less statistic withlabel,value,unit,hint,trend,size(sm | md | lg),alignandskeleton. It renders a description list (label<dt>, value<dd>), has no surface, border or padding, uses tabular numerals, truncates the label and never wraps the value.KpiCardis now aStatinside aCard, so both share one typography: the doubled inner padding (card padding +.ca-kpi__innerpadding) is gone, and the unused.ca-kpi__topand.ca-kpi__value-rowwrapper classes were removed;.ca-kpi__title,__value,__unit,__trend(--up/--down)and__iconstay.b34ffd4:
StatandKpiCardtaketrendTone('positive-up' | 'positive-down' | 'neutral') so a trend can be good news when it falls (churn, cost, latency). The arrow still follows the sign, the colour follows the tone (hook:data-tone="good|bad|neutral"on.ca-stat__trend) and the accessible label says better or worse (Trend: -1.8%, better,Tendência: -1.8%, melhor). Without a tone nothing changes. Apps that droppedtrendand wrote the delta inhintcan pass it again.7bc89c7:
TextField:revealableadds a show/hide eye button totype="password"(aria-pressed, localized name, 44px hit area on touch, no layout shift),belowrenders a snippet under the field and its hint or error (a "Forgot password?" link, a strength meter) andrequiredIndicator={false}hides the asterisk of arequiredfield. New i18n grouptextField(en, pt-BR) (#348).3aacf62:
ThemeIsland: a block or a whole page with its own preset and scheme.documentalso mirrorsdata-preset,data-theme-scopeand thedarkclass onto<html>while mounted (sohtml/body, overscroll andcolor-schemematch) and restores the app's values on unmount, with overlapping islands and theme-store writes handled. SSR parity:setRouteTheme(event, { preset, scheme })in aload, orcaThemeHandle(theme, { routes })by route id prefix, writes the island on<html>in the server response, and the init script and the Astro<ClientRouter />swap keep the app's own preset aside for the navigation back. The docs page explains how nested presets anddata-theme-scoperesolve (#352).c39a426: New
ColorField(swatch, native picker,#hex/rgb()/oklch()entry and a live WCAG contrast readout) andSliderField(label, slider and an editable number with a unit).Slidertakes alabelso a thumb is announced as "Radius" instead of "Value 40". The color helpers behind them are public:parseColorInput,contrast,wcagLevel,toHex,toOklch,formatOklch,extractPaletteandpickBrand(derive a brand from the pixels of a logo), pluspresetContrastIssuesandpresetTokensfor theme editors.37cda52: toast: optional
description(secondary line) and a single inlineaction: { label, onClick }ontoast.showand thesuccess/error/warning/infoshortcuts, which now also take the options object as the second argument (positionaldurationstill works). Clicking the action runs it and dismisses the toast; an actionable toast lasts 8 s by default. The auto-dismiss timer pauses while the toast is hovered or focused and resumes with the time left (toast.pause(id)/toast.resume(id)), and Escape dismisses the focused toast. Errors and warnings are nowrole="alert", the others stayrole="status". The action is a text button with a 44px touch target on touch devices.15074bf: A named type scale by role.
title,heading,body,label,captionandnumare size / leading / tracking / weight bundles (--ca-text-<role>-size|leading|tracking|weight, and--ca-text-<role>as afontshorthand), applied with<Text role>or.ca-text-<role>;numrenders tabular digits so a ticking counter never shifts its neighbours.ThemeOptions.type = { label: { size: "13px", leading: 1.25, tracking: "0", weight: 500 } }overrides any part for the whole app, library components included:--ca-font-size-label,-body1,-captionand--ca-label-trackingare now derived from the roles, with the same default values, so existing components follow without edits. Unknown roles and values that are not a length, number or keyword throw at build time.
Patch Changes
817b5c4:
get_recipe,llms.txtand the decision tables now teach the 4.6 APIs: a map-first recipe (AppShellLayoutstage,MapStage,PageFrame), rail labels and the "More" sheet,ChipGroup,Stat, dense tables,Kanban,Drawer chromeless, toast actions,tone="neutral",ThemeIslandand per-preset button colors. The "common workarounds" page lists what each one replaces.caf1fa7:
AppListis generic:Tis inferred fromdata, so thelistof itsfilters,childrenandemptysnippets is aUseListStateReturn<T>(rows are typed,searchKeysandidKeyare checked againstT) instead ofany, and a snippet annotated withUseListStateReturn<User>type-checks. The recipes now keep app copy in a typedsrc/lib/messages.ts, so they passcodeatlas-ui lint; the new App copy page explains thei18n-literalrule and that pattern.7667a22:
AppShellLayout: only the most specific nav item is active. With items at/itemsand/items/a, visiting/items/amarks justItem A(aria-current, active style, section auto-open) and the mobile header title is its label, no longer the overview's. Same rule as modules: longest path wins whatever the order. The overview item stays active on its other sub-paths.findActiveItemPathfollows it and the newfindActiveItem(entries, pathname)returns the item itself.2ccbb9f:
caUI(Astro) now puts the theme first in<head>, ahead of every stylesheet. It usedinjectScript("head-inline"), which Astro renders AFTER every bundled<style>: withinlineStylesheets: "always"the init script sat behind hundreds of KB of CSS and ran only once the browser had parsed it, so a slow link could paint the wrong theme first. A middleware now rewrites the rendered page, at build time and in SSR and dev alike, so the order holds whatever the layout or the bundler does. Font preloads move ahead of the CSS too.caUI(theme, { head })takes extra first-paint markup (a boot script) that lands right after the theme.caThemeHandleandcaThemePluginalready emitted at the start of<head>.6d65542:
AuthLayout: abrandsnippet now replaces the whole logo. The default "CodeAtlas UI" name no longer renders beside it, in the side panel or the mobile strip; passlogoNameto show a name next to your brand (#340).06fb8aa: AuthLayout: on a flat panel (
panelTone="flat", orautounder theflatandoutlinedsurface styles)--panel-text-secondaryand--panel-text-tertiaryare the theme's--ca-text-secondaryand--ca-text-hintinstead of alpha mixes of the foreground, which measured 4.39:1 and 2.4:1 in dark and 2.21:1 in light over the raised surface. The brand gradient keeps the alpha levels.2b04e94:
AuthLayout: the compact mobile brand mark now sits on--ca-brand-fill(the surface--ca-brand-contrastis paired with) instead of--ca-brand-main, so its label holds 4.5:1 in dark on every preset. It failed color-contrast in dark at 390px on six of the eight built-in presets.1cf8c69:
--ca-brand-textis now solved per preset and scheme instead of aliasing--ca-brand-dark/--ca-brand-light. A light brand (lime, yellow, light cyan) no longer gets a 1.6:1 label on outlined and ghost buttons, links and active nav: the shade is darkened (light scheme) or lifted (dark scheme) along the brand hue until it holds 4.5:1 on every surface and on the hover/selected fills. Built-in presets keep their light value; dark values lift slightly where they were under 4.5:1.tests/contrast.test.tsmeasures every preset, light brands andcolors.<scheme>.brand.35b5c9b:
<Button type="submit">and<Button type="submit" form="id">type-check in apps withoutstrict(the SvelteKit 2 scaffold): svelte-check widens a statictype="submit"tostring, which matched neither branch of the Button props union (formwasnever). The button branch now accepts the widenedstring. Documented on Button and Dialog: a submit button in a Dialogactionsfooter names its form withform.ad005c0:
CardandGlassCard: the header no longer adds its own padding on top of the card padding, so the title shares the left edge and top spacing of the content (#341).flush/padding="none"cards keep the header inset. NewCardpropgap('sm' | 'md' | 'lg' | length | number) stacks several inline controls (Checkbox, Switch, TextField) in a column.419d775:
Carousel: the slide strip is now the focusable region (axescrollable-region-focusable; arrows, Home and End work while it has focus, also when no slide holds a control), it no longer widens a flex or grid slot to the slides' width, and previous / next / dots reach the 44px touch target.Menubaritems get the 44px hit area under a coarse pointer.TextFieldcan shrink inside a grid or flex slot narrower than the input's intrinsic width (addon fields overflowed phones).c39a426: Charts follow a theme scope. They read their colors with
getComputedStyleon<html>, so a chart inside aThemeIslandor anydata-preset/data-theme-scopeelement painted the page's colors, and its series palette ignored the island's preset.ThemeIslandnow provides its element as the scope, and the newChartScope(from@codeatlasdev/ui/charts) does the same for your own scoped containers. Without a scope nothing changes.73f38cd:
CodeSnippethighlights any language shiki bundles (markdown,yaml,diff, ...), loading it on first use, and falls back to plain text for a name it does not know. Before, a language outside the eight preloaded ones made the highlighter throw inside the promise and the snippet stayed on its unhighlighted fallback for good.73f38cd:
CodeSnippet: a code block that scrolls sideways is keyboard-reachable. The raw fallback shown before the highlighter loads (and the highlighted block) now usescrollFocusable, so a long line no longer leaves a scroll region that only a mouse can reach (axescrollable-region-focusable).475158b: The stylesheet drops 12 utility classes that no component, template or documented API ever used:
.glass-bg-extend,.glass-inner-sm|md|lg,.glass-prominent,.glass-scroll-edge-top|bottom,.ca-toolbar-sep,.ca-clear-btn,.ca-row-check,.ca-trend-up|down.styles/global.cssshrinks by 242 B gzip (17458 B to 17216 B) and its size budget goes back from 17500 B to 17250 B. An app that styled an element with one of these classes keeps the rule in its own CSS.6aafbb5: A page with no
data-styleon<html>now renders asdata-style="solid", the package default: no backdrop blur, opaque surfaces, no glow, the pulsing skeleton. Before, an app without the theme plugin (nothemeScript, nocaUI) kept the glass tokens, so everydata-ca-blurelement (chips, tabs, toasts, rail, panels) blurred. Glass, frosted and the other styles are unchanged: setdata-styleto opt in.ce8ed51: Docs, skill, AGENTS.md, README, MCP
list_surfacesand component JSDoc present the library as theme-driven withstyleas one knob (defaultsolid) and glass as an opt-in style. Glass-only rules (backdrop-filter,data-ca-blur,will-change) are scoped to a "Glass and frosted styles" section with their wording unchanged. Examples, templates and demo specs no longer pinmaterial="glass". Theliquid-glassnpm keyword is replaced bytheminganddesign-tokens. No API changes.866912f:
FileUpload: the drop zone's accessible name now includes the visible hint (aria-labelledbyover the label and the hint), fixing the axelabel-content-name-mismatch/ WCAG 2.5.3 failure (#490). With a customchildrensnippet the zone keepsaria-label.fbc0d75: The gallery filters by facet. Chips for kind, subtype, style, motion, interaction, layout, colour mode, density, tech and cost narrow the cards instantly, show how many entries each value would match, and drop combinations that match nothing. Words match titles, aliases, tags and components. Facets, words and the group live in the URL (
?kind=motion&style=aurora&q=fade), so every filter is a shareable link. Only published facets are offered.99a3dbd: A gallery item opens on Preview, Code, Prompt and Anatomy tabs (
?tab=prompt,?tab=anatomy). Prompt is the text to hand an AI: theget_democall, the library parts, the behaviour to keep, the exact motion timings and the reduced-motion and keyboard promises read from the index. Anatomy lists the components with their package and docs link, how the demo is built, and related items. Pages show Preview and Prompt.678d673:
init --writeon SvelteKit 2 with@sveltejs/adapter-staticcreatessvelte.config.jswith the adapter andvitePreprocess, or addsadapter: adapter()and its import to an existing config that names no adapter. Before,vite buildran with no adapter and nobuild/output. A config that already setsadapteris untouched; Kit 3 is unchanged (the adapter goes intovite.config).7fe318d:
initon SvelteKit adds"prepare": "svelte-kit sync"topackage.jsonwhen the app has nopreparescript, sosvelte-checkon a fresh clone no longer stops withFile '$app/tsconfig' not found.src/lib/messages.tscarries onlyappTitleunless--with pwais given: theofflinekeys are written (or added to the fileinitwrote) only with the offline page that reads them.b8e217e: Surfaces and chips inside a
ThemeIsland(and aPreviewStagewith aschemeorpreset) follow the island, not the page around it. The layer tokens (--ca-overlay-bg,--ca-panel-bg,--ca-chrome-bg,--ca-upload-bg) and the solid style's tint overrides are re-declared on[data-theme-scope], so a dark island on a light page no longer inherits the page's resolved light surface (aFileUploadzone, anAppFormfooter) under dark text. Thesuccess,error,warningandinfochip text follows[data-theme-scope]as well as.dark.fe9c5d3:
Kanbanno longer collapses to 0px wide inside an auto-width flex wrapper (a wrapping flex row, an island): the size-contained board now has a one-column floor width (contain-intrinsic-inline-size), while a narrower parent still shrinks it and the columns never widen the page (#530).7873a89: The Landing page recipe links only to in-page anchors and to
/sign-inand/sign-up(the Auth pages recipe), so copying it whole prerenders withadapter-static; before,/pricing,/docsand/contactfailed the build with a 404.85086c5: The Landing page recipe now prerenders when copied on its own: the sign-up and sign-in buttons and the plan CTAs use
signUp/signInconstants at the top of the page that are in-page anchors (#pricing), instead of linking to/sign-upand/sign-in, which 404 the prerender unless the Auth pages recipe is also in the app. Point the constants at the real routes once they exist.82edb9d: The close button of
Dialog,AlertDialog,DrawerandSheet(.ca-layer-close, drawn 28px) gets the invisible 44px hit area under a coarse pointer, like every other small control. It was missing from the touch list, so an overlay measured 27 by 27 on a phone, inline overlays included.b8edc32: A tooltip or hover card that was open when its trigger opened a dialog no longer leaves a second, stray copy in the page after the dialog closes. The layer stack put a hosted hint back at its old position even when the dialog had already been removed with the hint inside, resurrecting a node its owner had dropped (no
popover, in normal flow, sameid).503a842:
init --writeadds the"imports": { "#lib/*": "./src/lib/*" }map topackage.jsonand writes#lib/theme.ts/#lib/messages.tsimports on SvelteKit 2 as well as 3 (Kit 2's Vite and TypeScript resolve the subpath import, verified by building and type-checking a Kit 2 app). The recipes, app-copy, installation and PWA pages now use the one spelling#lib/...tson both majors instead of telling Kit 2 users to rewrite every import to$lib. An existing$libimport on Kit 2 keeps working and is never rewritten.9e751a2:
llms.txtand theAGENTS.mdblock written byinitsay thatdist/components.jsonkeeps the components under.components[]and every other export under.exports[].3b76318:
llms-full.txtkeeps only the props table of each non-core page. The summary also carried the prose and examples that follow a table (the SEO page alone added 9 KB), which pushed the file past its size cap once the Concepts pages were added; the descriptions stay in each page's own file.ce45681:
llms.txtlinks the agent-readable.txtform of every docs page (/llms/<group>/<slug>.txt, index pages/llms/<slug>.txt) and of each recipe (/llms/recipes/<id>.txt), with the HTML URL beside it, and states the fetch rule at the top.dist/components.json(andget_component) now list the layout token props ofFlex,Stack,Grid,Box,Container,SectionandSurface(gap,align,justify,direction,wrap,columns,p,px,py) from theirLayoutPropspick.b8edc32:
LoadingBoundaryholds the placeholder for the fullminDuration: the hold re-checks the clock when its timer fires and re-arms for the remainder, instead of ending up to a millisecond early when a timer fires ahead ofperformance.now().de1c873:
components.json(exports[].members),API.mdand the MCPget_exportnow list the fields of every exported interface (NavModule,NavItem,NavSection, option and state types) with type, optionality and JSDoc, instead of one collapsed line.573950f:
components.json,API.mdand the MCPget_componentspell out the values of a prop typed with a literal-union alias (Avatarshape: 'circle' | 'rounded',size,status,CardheadingLevel: 2 | 3 | 4 | 5 | 6,Buttonvariantand about 100 more) instead of the bare alias name, andAPI.mdkeeps the description of each snippet (SkipLinkchildrenreplaces the label).48e1d42:
@codeatlasdev/ui/map:MapStageworks withmaplibre-gl6, so the peer range is^5.0.0 || ^6.0.0again and you can take the fix for the critical attribution-sanitizer XSS inmaplibre-gl6.4.0 and earlier (GHSA-jrc7-96c5-q579, fixed in 6.4.1). Withengine="maplibre"the deck.gllayersnow go throughMapLibreOverlayfrom the new optional peer@deck.gl/maplibre(~9.4.0, which supportsmaplibre-gl4.5.1, 5 and 6) instead ofMapboxOverlay, which throwsCannot read properties of undefined (reading 'height')on every frame there.engine="mapbox"keeps@deck.gl/mapbox. Install@deck.gl/maplibreinstead of@deck.gl/mapboxwhen you usemaplibre-gl. Onmaplibre-gl6 the app must also callsetWorkerUrlonce (Vite:import workerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url"), as MapLibre's v6 migration guide requires for every bundler; the Map page shows it.b53e1c2:
MapStage: fitting clear of a widePageFrameno longer throwsInvalid LngLat object: (-Infinity, NaN). The stage set the map's padding and passed the same padding tofitBounds, which subtracted it twice and left a wide frame no area; the bounds now fit inside the visible area exactly once (#407).0169158:
nativeFeel()no longer forces the arrow cursor on buttons, links, tabs, menu items and options.html[data-ca-native]setcursor: defaultinca.base, which outranks the pointer cursor ofca.reset, so an app shell never showed the hand on clickable controls (nornot-allowedon disabled ones). The rule keeps blocking text selection and the touch callout on chrome; the cursor now comes from the reset layer alone.b8edc32:
NavigationMenu: Enter or Space on a trigger always toggles its panel. The click grace that lets a pointer click confirm a hover-opened panel also swallowed a keyboard activation made within 600 ms of a hover open, so a keyboard user could not close the panel.2ccbb9f: The theme init script no longer fails with the page. When
localStoragethrows on access (private mode, blocked cookies, a sandboxed iframe) it used to abort before settingdata-preset,data-styleand thedarkclass, so the page painted unthemed and then switched once the store hydrated. It now falls back to the theme's defaults on the first frame, and a failingremoveItemwhile discarding a corrupt value no longer stops the rest of the script. A size-limit entry pins the emitted script (under 1 kB gzip).colorScheme.setTokensnow actually applies in dark mode with a brand preset. Runtime tokens were written as:root{…}(specificity 0,1,0) and lost to the preset's.dark[data-preset]block (0,2,0) whatever the source order, so the stored tokens replayed by the init script, and the ones the store wrote, silently did nothing. Both now use:root:root:root{…}.8e950af:
NotificationBell: the trigger's accessible name now starts with the visible unread count (2 unread notifications/2 notificações não lidas, plainNotificationsat zero), so it contains the visible badge text (WCAG 2.5.3, axelabel-content-name-mismatch). CustomnotificationBell.labeloverrides should keep the count in the name.ae3b5bd:
PageFrame: on mobile (<=900px) the full-screen panel and the bottom sheet no longer overflow the viewport by the frame gap withside="left". The desktopdata-sideposition rules out-ranked the mobileinset: 0reset, so the panel keptleft: 12pxwithwidth: 100%(right edge 402px on 390px). Fixes #489.57ff10e: PageShell: below 900px only
Buttons inactionsstretch across the row.StatusChip,Chip,Badgeand plain text keep their natural width instead of becoming a full-width bar (#339).e5cb35d:
PageShellandPageFramewarn once in development when the page has no<main>landmark (nomain/[role=main]ancestor and none in the document), pointing at the Standalone page recipe. Silent insideAppShellLayout,AuthLayout,DocsShellor your own<main>.llms.txtlists Standalone page first under "Start here".99ab5db:
.ca-proseand.ca-codefences: thecodeinside apreinherits the pinned--ca-font-monostack instead of the browser's baremonospace, which Chromium could resolve to a different font (and line height) between two paints.0168dcf: Theme:
--ca-font-size-body1no longer sets the root font-size.:rootkeeps the kit's rem base (calc(0.9375rem * var(--ca-font-scale)), 15px by default, still relative to the user's browser size), so default rendering is unchanged and overriding body1 only sizes the text onbody. A denser body no longer shrinks labels, inputs, buttons and captions (#351).b8e217e:
scrollFocusablere-measures when content is added at any depth, not only as a direct child of the scroller. A fixed-height wrapper never resizes when the content it holds grows, so a lazily loaded demo inside aPreviewStageframe left its scrollable region without a tab stop.b8e217e:
PreviewStageframes andResizablePanels that scroll are keyboard-reachable while their content overflows (scrollFocusable, WCAG 2.1.1): a demo taller than the stage, or a narrow panel whose content is wider than it, no longer traps its content out of keyboard reach. Without overflow they add no tab stop.b8e217e:
SegmentedControlitems are at least 44px wide as well as tall under a coarse pointer: in a narrow slot the equal shrinkable columns squeezed short labels (a "390" preset) to 38px, under the touch target.03c5f65: The Settings page recipe no longer tells a
Switchinside aSettingItemto carry its ownaria-label: the row is a<label>and already names it (as the SettingItem page says), so the redundant attribute and the rule are gone.f45caa6:
SiteHeader: aNavigationMenuplaced directly intrailingis hidden below 600px, andtrailingcan shrink (min-width: 0). A marketing bar with three nav links, aLocaleSwitcherand two buttons overflowed the page horizontally at 390px (right edge 437px); the locale switcher and the buttons now stay in the bar and the links belong in a mobile menu of your own (#476).169db3e: toast: identical messages no longer stack.
toast.error("x")ten times leaves one toast with a×10badge and a restarted timer, at most three toasts show at once (the oldest is dropped), and{ id }replaces a specific toast in place. The shortcuts (success,error,warning,info) also accept an options object ({ duration, id }) as the second argument; positionaldurationstill works.ToastItemgainscountandToastOptionsis exported.9a10232: Touch targets: the password reveal button (
TextField revealable) gets its 44px hit area on touch again. A merge had fused two selectors in the coarse-pointer list (#417).b8edc32:
VirtualListno longer drags the user back down after older rows are prepended. A prepend always takes its own scroll anchor, and a restore that was waiting for its tick when the prepend landed no longer applies the anchor it captured against the old offsets (the list jumped to the old first row, about 20 rows down, and skipped the nextonloadmore).cb6da91:
VirtualList: a scroll made while the anchor restore after a prepend or a row re-measure is still queued is no longer undone. On a slow machine the late restore dragged the list back to the old anchor, so a user scrolling to the top to load the next page was thrown ~1000px down.
4.5.0 latest
Minor Changes
- c23cff4: i18n: the message catalog is split per group and each component bundles only its own strings.
root: Button onlydrops from 27.89 to 23.91 kB gzip andAppShellLayoutfrom 39.88 to 36.07 kB.Messages,getMessages,useT,I18nProvider,setLocaleandregisterLocalebehave as before;useT()andgetMessages()still return the full catalog, which is bundled only when you import them.
4.4.0 latest
Minor Changes
1c6653e:
NavItemin the AppShell panel getscolor(a decorative dot),leading(a snippet that replaces the icon),count(a neutral total, announced as a bare number instead of "N unread",1.2kabove 999) andactions(a snippet ofDropdownMenuItems behind a "…" button named "Actions for <label>") (#275).NavSection.onReorderandNavModule.onReordermake the rows reorderable with theuseDndprimitive: a grip per row, mouse, touch press-and-hold and keyboard, with announcements; the shell reports{ id, from, to }(newNavReorderEventtype) and never reordersitemsitself.PanelNavItemnow renders a row that contains the<a class="ca-nav-item__link">(a link cannot hold the actions button or the grip), so.ca-nav-itemis the row, not the anchor. New messagesappShell.itemActionsandappShell.moveItem.1c6653e:
AppShellLayouttakespanelWidth(px, held between 160 and 480, default 200) andresizablePanel(#292). The content offset and the open and close animation follow the width, so a panel for long labels no longer misaligns the page.resizablePaneladds a keyboard-operable drag handle on the panel's trailing edge (Arrow keys 8px, Shift 32px, Home and End for the bounds); withpersistPanelthe chosen width is stored inca-shelland restored by the inline script before the first paint. NewappShell.resizePanelmessage and.ca-panel__resizeclass.ca80cfd:
Combobox:multipleturns it into a searchable multi-select (valueisstring[],aria-multiselectablelistbox, list stays open, removable chips,Backspaceon an empty query removes the last chip) andcreatableworks in both modes.CommandPalette:search(query, signal)adds debounced (searchDebounce), abortable server-side results with loading, empty and failed states announced throughannounce(), plusonSelectand the exportedCommandResulttype.49116d1:
DataTablegains row groups, column resize, row reorder and safer interactive cells (#283).groupByrenders one<tbody>per key with a collapsible header (aria-expanded,groupLabelto customize).meta.resizableon a column adds a window-splitter handle (mouse, touch, keyboard arrows, Shift, Home, End, Enter) driven bysize,minSizeandmaxSize; the newDataTableColumnResizeis exported for hand-built headers.onReorderadds a drag grip per row on top of the newuseDndprimitive, moves rows between groups and reports{ id, from, to }(DataTableReorderEvent);rowIdgives rows stable ids andpageSizesets the page size in simple mode. A click on a link, button, input or other control inside a cell no longer firesonRowClick(adddata-no-row-clickto opt other elements out). The new labels (Reorder, Move, Resize) follow the active locale (en, pt-BR). Needs the drag and drop primitive from #284.d138bd8: New drag and drop primitive,
useDnd(),applyMoveand<DndLayer>, for sortable lists, moves between lists and drop zones (#284). It works with mouse and pen (4px threshold), touch (press and hold, so the page still scrolls) and keyboard (Space lifts, arrows move, Home/End jump, Enter drops, Escape cancels), speaks every step throughannounce()(English and Portuguese built in,useDnd({ messages })for others), auto-scrolls near the edges of a scrolling container and drops its animations underprefers-reduced-motion. The engine measures the boxes once per drag and only moves a clone, a drop line anddata-dnd-*attributes, so the list does not re-render untilonDrop.<DndLayer {dnd} />renders the keyboard instructions and the forced-colors aware styles (.ca-dnd-overlay,.ca-dnd-indicator), so apps that never drag add no bytes tocore.cssor to the i18n catalog.946d69d:
Drawercan be named without its default title: passaria-label,aria-labelledby(your own header) orhideTitle, andid,data-*and other attributes now reach the<dialog>(#285).AlertDialoggainsconfirmPropsandcancelProps(attributes for the two buttons) andcancelVariant(#287), plus achildrenbody, adescriptionthat accepts a snippet andconfirmDisabledfor type-to-confirm (#278).e983caa:
DropdownMenuis controllable and can open at a point.bind:openandonOpenChangeread and set the open state;anchor={{ x, y }}opens the menu at a viewport point (a right-click menu, notriggerneeded) and changing it while open moves the menu instead of toggling it. A menu opened from code starts on its first item and returns focus to the element that had it. On the built-in trigger Shift+F10 and the ContextMenu key open the menu like ArrowDown.e983caa:
DropdownMenugains selection items.DropdownMenuCheckboxItem(role="menuitemcheckbox",bind:checked,onCheckedChange) andDropdownMenuRadioGroup+DropdownMenuRadioItem(role="menuitemradio",bind:value,onValueChange) renderaria-checkedand a check indicator, stay open after a pick (closeOnSelectcloses), and join arrow, Home, End and typeahead navigation (the menu now also recognizesmenuitemradio).e983caa:
DropdownMenugains submenus.DropdownMenuSubtakes atriggersnippet holding aDropdownMenuSubTriggeritem (aria-haspopup="menu",aria-expanded, chevron) and the submenu items as children; they nest to any depth. ArrowRight, Enter or Space opens the submenu on its first item, ArrowLeft or Escape closes only that level, hovering the trigger opens it after a short intent delay and the pointer can cross into it. Selecting an item closes every level. A popover layer that is closing no longer owns Escape or outside presses, so a closing submenu hands them to its parent.ff460ad: Form controls forward native attributes and expose their element, with one vocabulary (
ref,hideLabel, rest props on the native element):Checkbox,TextField,TextAreaandFileUploadspread every other attribute and handler (data-*,maxlength,inputmode,onclick,onkeydown,name,capture...) on the native<input>/<textarea>, and take a bindablerefwith that element.Checkboxonchangenow receives the native event as a second argument, and its forwardedonclickmakes Shift range selection possible.TextField/TextAreagainonfocusandhideLabel(label kept for screen readers with.ca-sr-only;aria-labelalso works without a label).Selectacceptsaria-labelandaria-labelledbyfor a compact select without a visible label. The trigger is named by that text followed by the current value (visible text stays in the name); without either, the placeholder names it as before.FileUploadgainsref, anopen()instance method for a button outside the dropzone, andlist={false}: a plain picker whoseonchangereceives only the files just picked, with no built-in list and nothing kept infiles(the native input is cleared, so the same file can be picked again).
8c24c31: Theme option
motion: { enter: "none" }(token--ca-enter-scale: 0) turns off entrance and exit animations only:Dialog,AlertDialog,Drawer,Sheet,Lightbox,CommandPalette,DocsSearch, date pickers,DropdownMenu,Popover,TooltipandToastappear and leave in place, while hover and focus color transitions keep--ca-duration-*. New derived tokens--ca-enter-fast,--ca-enter-baseand--ca-enter-slow;MotionStagedemos render their final state when the theme turns entrances off, as underprefers-reduced-motion(#268).16f89dd:
Popoveris controllable.bind:openandonOpenChangeread and set the open state, and thechildrensnippet receivesclose, which closes the popover and returns focus to the trigger, so a choice made inside it (aCalendarday, an option) can dismiss it. Existing popovers are unchanged.8c24c31: New
--ca-radius-roundtoken (999px × --ca-radius-scale): "fully rounded at the current scale".SwatchGroupswatches, the AppShell active pips (.ca-nav-item__pip,.ca-rail__pip) and the new-item dot, and.ca-nav-item__badgeuse it, so a theme withradius: 0squares them with no app CSS (#267).--ca-radius-chipnow aliases it.--ca-radius-pillstays a fixed 999px for functional shapes (Switch, Slider, Progress, scrollbars) and forcontrolRadius: "pill".946d69d:
Tabsgainsvariant="line"(bare text tabs with an underline on the active one, for page headers), andTabsListandTabsTriggerforward rest attributes:aria-labelnames the tablist,data-*,idand event handlers reach the trigger button (#286). ATabsTriggernow pointsaria-controlsat its panel only while aTabsContentwith the samevalueis mounted, so tabs used as view switchers no longer reference missing elements.2ab0d93:
ToggleGroupgetsrequired: pressing the active item no longer clears the selection (radio-like,onchangestays silent;type="multiple"keeps its last pressed item). An empty group still selects on the first press. Closes the gap withSegmentedControlfor filters that must look like contiguous buttons.
Patch Changes
- 05cfbfc:
AppShellLayout: an item atpath: "/"is active on/only, no longer on every page, and a module atbasePath: "/"only gets the pages no other module claims. Module prefixes (basePath,pathPrefixes) match whole path segments and the longest one wins, whatever the module order (/settingsfoono longer matches/settings). - 98d2ace: Astro: the theme now survives
<ClientRouter />(View Transitions) navigations.caUIregisters anastro:before-swaphandler that carries#ca-theme,#ca-runtime-tokens, thetheme-colormeta and the theme attributes onto the incoming document (#265).codeatlas-ui initon Astro also addssvelte()from@astrojs/sveltetointegrations, and notesnpx astro add sveltewhen the dependency is missing (#273). - 3ae1f41:
Buttonwithhrefnow types the anchor's own attributes:download,rel,hreflang,ping,referrerpolicyandtype(the linked resource's MIME type) are accepted instead of rejected, as...restwas already spread on the<a>. The props are a union keyed onhref: a link takes anchor attributes, a button takes button attributes (form,formaction,name,value...), and<Button download>withouthrefis a type error. At run time atypepassed withhrefreaches the<a>, and a callerrelis kept and merged withnoopener noreferrerwhentarget="_blank"(it used to be overwritten). - 5c9ac81: CommandPalette: new
shortcutprop (defaulttrue). With two palettes on one page, every instance opened on Mod+K; setshortcut={false}on all but one, or when the app binds its own shortcut. - a61874b: Calendar, RangeCalendar, DatePicker and DateRangePicker: fast arrow-key sequences no longer lose keys. The roving focus now moves synchronously instead of after
tick(), so the next key always starts from the day it lands on, with or without Svelte'sexperimental.async. - e983caa:
DropdownMenuItemforwards its remaining attributes (data-*,id,aria-*,title, ...) to themenuitemelement.data-testidandaria-keyshortcutsused to be dropped.role,tabindexand the selection handler stay owned by the item. - ff8686f:
DropdownMenuItemandTabsTriggericon slots now size anysvgorimginside the 14px box, not only a direct child. ASchemeAssetlogo (its variants sit indisplay: contentsspans) used to collapse to 0×0 when the svg had nowidth/height, and an svg with a fixedwidth="24"overflowed the box; both now fill the box. - d01c039:
LoadingBoundarycountsminDurationfrom the moment the placeholder reaches the DOM instead of from the state change that schedules it, so a busy main thread no longer shortens the time the placeholder is visible. - 23276cf: Fix Escape going to the wrong layer. A Dialog, AlertDialog, Drawer, Sheet, Lightbox, CommandPalette or DocsSearch that is playing its exit animation no longer takes Escape or presses: they reach the layer below, so closing a confirm dialog and pressing Escape right after closes the Drawer under it (#288). A menu or popover that was open when a modal opened now ranks above that modal, so Escape closes the menu first and the dialog second instead of closing the dialog and leaving the menu open (#299). Exit animations of components that use
createPresence(Dialog, Drawer, AlertDialog, menus, popovers) now keep the node mounted through the exit instead of removing it in the flush that closes it, and a menu hosted in a dialog is no longer left stranded after its exit. - 05cfbfc:
Rail: the dock magnification scales the module icon (the glyph), not the whole button, so the hovered background no longer covers the active neighbour's background and pip. The effect follows--ca-motion-scale: it is off withmotion: { scale: 0 }and underprefers-reduced-motion. - 05cfbfc: Native
button,input,selectandtextareainherit the page font (font: inheritin the newca.resetlayer,styles/reset.css) instead of the browser's UA font. The AppShell section header (.ca-section__trigger/.ca-section__label) and any control without its own font rule now render in--ca-font-sans. Component and app rules still win: the layer is the first one. - 8c24c31:
Skeletonand theskeletonprop follow the theme surface style: underflat,solidandoutlinedno gradient band is drawn, the placeholder is a flat block that pulses its opacity (ca-skeleton-pulse, stopped under reduced motion). Glass, frosted, neumorphic, clay, chrome and tonal keep the shimmer sweep (#280).
4.3.0 latest
Minor Changes
- a1c0d04: Demo sidecars accept
names(motion vocabulary) andmotion(exact property, duration, easing, delay, stagger, spring). They are printed ine/NNN.txtandp/NNN.txt, carried incatalog.jsonandcatalog.tsv, andca-ui-mcpsearch_demosmatches names and aliases before summary and tags. - a1c0d04:
MotionStageand theMotionCtxtype: the frame of a motion demo. It autoplays only while at least half of it is in view and the tab is visible, hands control to the user on pointer, key or focus (autoplay stops, Replay appears), always shows a Pause button witharia-pressed, and underprefers-reduced-motionnever autoplays and tells the demo to render its final state (ctx.reduced).ctx.cycleincrements each loop so a demo can re-run its animation. Labels come from the i18n catalog (motionStage.pause,motionStage.replay) and can be overridden withpauseLabel/replayLabel.
4.2.0 latest
Minor Changes
- b65d56d: AI feedback loop. The
ca-ui-mcpserver has a newreport_issuetool: an agent that hits a library problem (bug, missing prop or component, migration gap, wrong docs) gets a sanitized issue draft (paths, foreign URLs, emails, IPs, tokens, env assignments and foreign package scopes are redacted, and the redactions are listed) with a prefilled link to the new "AI report" issue form and agh issue createcommand. It never posts.bunx @codeatlasdev/ui workaroundslists the// ca-ui-workaround(#<n>): <why>markers an agent left in your code and which issues are already closed (read-only, offline-safe, exit 0). - 81910d1: Avatar renders the photo as a real
<img>(no detachedImage()probe, so no double download) and passesloading,referrerpolicyandcrossoriginthrough, soloading="lazy"keeps a 200-row list from firing 200 requests andreferrerpolicy="no-referrer"works with CDNs that reject a cross-originReferer.fallbacknow also takes a snippet (an icon for groups, channels and bots), andonerrorfires when the image fails so you can refetch a signed URL. The fallback is always rendered beneath the image, so the box never changes size. Thedata-statusattribute on.ca-avataris gone. - 80c0336:
Cardhas apaddingprop ('none' | 'sm' | 'md' | 'lg', any CSS length, or a number of pixels) so a one-off density no longer needs an inner wrapper;flushstays as the alias of'none'. The docs now say that Card's content is already padded, and in development a[ca-ui]warning (card-double-padding) fires once when the first child adds its own padding on top of the card's (the 20px + 20px = 40px "oversized card"). - cd655bb: Chat primitives:
MessageBubble(direction, grouped tails, sender and avatar slot, quote, reactions, delivery ticks),ConversationItem(inbox row with unread, pinned, muted, typing, active),AudioPlayer(lazy src, waveform frompeaks, slider semantics, playback rate),MediaGrid(image, video and document tiles opening aLightboxgallery) andVirtualList(windowing plus load-older at the top that keeps the scroll position). Newchat,audioPlayer,mediaGridandvirtualListmessages in en and pt-BR. - 80c0336:
CopyButtontakesaria-labelfor the icon-only form (label=""), so each row of a list or table can announce its own name ("Copy client_id of Acme") instead of the same localized "Copy". The default stays the i18ncopyButton.label. The label is ignored whenlabelis visible, so the accessible name keeps containing the visible text. Remaining attributes (data-*,id,aria-describedby, …) are now forwarded to the underlying button. - 6e08799: Every numbered demo now has a reviewed
<Variant>.meta.jsonsidecar (summary,use,spec,tags,shape), soget_demo,search_demosand/api/v1/en/{e,p}/NNN.txtreturn what the demo shows, when to use it and a Spec list of its literal values (props, snippets, state, counts). The catalog check (manifest --check, run by every build) now fails on a demo without a sidecar, a sidecar without a demo, an unknown key, a badshape, or a spec line that quotes a token that is not in the demo source. Newcodeatlas-ui pick [ids...]opens the gallery, listens on127.0.0.1with a random per-run token and prints the ids you send with "Send to AI" (#005 #012). - d99cead:
codeatlas-ui initscaffolds SEO and PWA on SvelteKit (#205): prerenderedmanifest.webmanifest,robots.txtandsitemap.xmlroutes built with theseohelpers, a service worker (src/service-worker/index.tson$app/service-worker,$app/manifestand$app/envfor Kit 3;src/service-worker.tson$service-workerfor Kit 2) with a prerendered/offlinepage, and a default<Seo>with canonical and manifest link in the root layout. Existing files are never overwritten; after creating theminitreminds the app to setpaths.origin(otherwise Kit's prerender placeholder origin reaches the sitemap and canonical URLs), add the icons and excludesrc/service-workerfromtsconfig.jsonon Kit 3. - d99cead: New
InstallPrompt(#208): a dismissible banner that offers to install the app. On Chromium it keepsbeforeinstallprompt(no browser mini-infobar) and shows an Install button that opens the native dialog and reports the choice withonchoice; on iOS Safari and iPadOS, which have no install API, it shows the Share, Add to Home Screen steps. It renders nothing when the app already runs standalone, and stays hidden fordismissDays(default 14) after the user closes it. Strings are in theinstallPrompti18n group (en,pt-BR); theroot: Button onlysize budget rises by 240 B gzip because both catalogs ship with every root import. - 81910d1: Lightbox opens videos and galleries.
items({ type?: 'image' | 'video', src, alt?, caption?, meta?, poster?, download? }, exported asLightboxItem) plus a bindableindexadd previous/next buttons, ArrowLeft/ArrowRight and swipe (no wrap; the end buttons arearia-disabledso focus stays put), a counter, caption and sender/time line, video with controls (paused when you move on), and a Download button (ondownloador a plain<a download>).src/altstill work as a single-item shortcut. Newlightbox.previous,next,downloadandcounterlabels in en and pt-BR; the close button moved into a.ca-lightbox__toolsbar. - 3180023: Leading icon slot for
DropdownMenuItemandTabsTrigger, and a documented pattern for brand logos (#194).iconis a snippet rendered in a fixed 14px box with the standard gap andaria-hidden(the label stays the accessible name), so icon size and alignment no longer drift between menus.brandIconmarks the icon as a fixed-color brand mark: no highlighted, disabled, active or destructive color reaches it (trademark guidelines forbid recoloring). NewSchemeAssettakeslightanddarksnippets and shows the one for the kit's color scheme (.darkon<html>or adata-theme-scopeisland) with CSS only, so it is right in SSR and on first paint and apps no longer read the theme store for logo variants. New classes:.ca-dropdown-item__icon,.ca-tabs-trigger__icon,.ca-scheme-asset,.ca-scheme-asset__light,.ca-scheme-asset__dark. - 7a112eb: Navigation:
LoadingBoundarywraps<svelte:boundary pending>with a 200 ms delay and 500 ms minimum so a loading UI never flashes (default placeholder isSkeletonrows,aria-busy, named region).@codeatlasdev/ui/kitgainsNavigationProgress(top bar driven bynavigatingfrom$app/state) and thependingLink()attachment that setsdata-pendingon the link being navigated to. A test now proves AppShell stays mounted across 10 client-side navigations. - df50d86: New
RecordList,RecordRowandRecordCell: records as dense rows (identity, a few columns, a row menu) that re-flow to a compact two-line row on a phone. The list owns the flush container, the separators, onecolumnstemplate shared by the header and every row, the re-flow (a container query on the list's own width, 36rem), the 44px actions cell and anaria-hiddenheader whose labels become per-celldtwhen stacked. It replaces the hand-rolled flushCard+ grid +:globalrow CSS, and is much shorter on a phone thanTable responsive="cards". - d99cead:
<Seo>completes the social and language tags (#207):og.defaultImageis the site-wide card used when a page has noog.image(also feedstwitter:imageand thesummary_large_imagecard),twitter:titleandtwitter:descriptionmirror the page title and description, and the newlangprop setsog:localeand keeps<html lang>in sync on the client. - 81910d1:
skeletonprop onAvatar,KpiCard,TextField,Table,CardandGlassCard: the component keeps its real DOM, so the placeholder has exactly the loaded size (no layout shift). Content is laid out but hidden, the host paints the same shimmer as<Skeleton>, and it is announced witharia-busy="true"; a skeletonTextFieldis disabled and anAvatarmakes no image request. Reduced motion stops the sweep and forced colors draw an outline. New global[data-skeleton]rule andca-shimmerkeyframes inutilities.css. - f889334: Every backdrop blur now comes from one place. Components no longer declare
backdrop-filter: they setdata-ca-blur="<role>"(or are a.ca-surface) andstyles/surface.csspaints it from the tokens<html data-style>rewrites, so one attribute turns every blur off or down together. The look is unchanged (guarded by a computed-style test over 9 styles, light and dark).FloatingLayergains ablurprop (overlayortooltip).lint:tokensrejectsbackdrop-filteroutsidesurface.cssandmaterials.css. The MCP server gainslist_surfaces, generated from the[data-style]selectors inmaterials.css, anddist/surfaces.jsonships with the package. - 3180023:
TabsListhandles its own overflow (#190). It keeps the width of its tabs insideTabs(no more full-column pill on desktop), and when the tabs do not fit it scrolls sideways on its own with the scrollbar hidden and scroll snapping, instead of wrapping or widening the page on a phone. The selected tab is scrolled into view on mount and when it changes (only the strip scrolls, never the page). New optionalfadeprop masks the edge that still hides tabs, like theAppSettingsnav. The wrapperdivwithoverflow-x: autoand the:global(.ca-tabs-list) { width: max-content }workaround can be deleted.
Patch Changes
- 526fee2: A menu, Select list, popover or tooltip that is already open when a Dialog, Sheet or Drawer opens (a shortcut or an async action opening the modal over it) now moves into that modal and stays clickable and above it. Before, it stayed outside the modal, so it went inert and could not be used. When a modal closes before the popovers hosted in it, they are handed to the next modal or put back where they were declared instead of being removed with it. The layer stack and the stacking contract are covered by new tests and documented on the Dialog page (#225).
- 3afd417: Overlays return focus to the control that opened them on iPhone/iPad and Safari too. WebKit does not focus a tapped button (it focuses a focusable ancestor such as
<main tabindex="-1">, or nothing), so focus used to land there on close and VoiceOver users lost their place. CommandPalette: Mod+K focuses the search field on touch devices with a keyboard (covered by a test now). - c76c837: New guide
getting-started/migrate-react, readable offline through the MCPget_guide("migrate-react")and on the docs site: decision flow (incremental or rewrite), React hook to Svelte 5 rune map with the effect pitfalls, MUI / shadcn-Radix / Chakra to@codeatlasdev/uicomponent maps, layout templates, theme mapping, TanStack Table to@codeatlasdev/ui/table, and how to report a gap (report_issuekindmigration-gapplus aca-ui-workaroundmarker). A unit test fails when a component named in the maps stops being an export. - d99cead: The manifest
generateBrand/caBrandPluginwrite is installable:display: "standalone"(was"browser"),idandscopeset to"/", andtheme_color/background_colortaken from the theme's default scheme instead of always the light one (#206). The icon hash now includes the default scheme, so switching it renames the files. - 81910d1: Skeleton's shimmer is a translated
::afterband instead of an animatedbackground-position, so it no longer repaints on every frame. Reduced motion still gets a static, dimmed placeholder. - 9c9df3b:
@codeatlasdev/ui/viteis safe in the SSR graph: importingdesignStyle,mergeThemeorThemeOptionsfrom atheme.tsthathooks.server.tsshares no longer fails the SvelteKit server build withUNLOADABLE_DEPENDENCYon@resvg/resvg-js(#184). The native peers are loaded by name at run time andcaBrandPluginmarks themssr.external, so thessr.externalworkaround is no longer needed.runBrandandbrandUrlsare now exported from@codeatlasdev/ui/vite, as the 4.0.0 changelog announced (#185).
4.1.0 latest
Minor Changes
- ccae97c: Numbered demo catalog (
ca-prompt): every docs demo has a stable#NNNid. The package shipsdemos.json(export@codeatlasdev/ui/demos.json) anddist/api/v1reference texts, andca-ui-mcpgainssearch_demosandget_demo. The agent skill documents picking demos by number.
Patch Changes
- 6ab52ee: Checkbox, Switch and RadioGroup: on touch screens (
pointer: coarse) a tap on the box toggles the control again. The 44px hit-area overlay painted over the native input and swallowed the tap; the native input now is the 44px hit area.
4.0.0 latest
Major Changes
- b99075a:
AppShellLayoutrenders its content region as<main id={mainId} tabindex="-1">by default, so a page built on it has a<main>landmark (axelandmark-one-main,region) and the skip link lands on it. It was a plaindiv. PassmainId={null}when your page renders its own<main id="main" tabindex="-1">: the region is then a plaindiv, never a second landmark. If you wrapped your content in<main>inside the shell, remove it or passmainId={null}. - fdb6798:
nativeFeel()now has one opt-out class,.ca-selectable, for both the context menu and text selection (#136). The context-menu filter used.selectablewhile the selection CSS used.ca-selectable, so neither class restored both. The filter and its docs now say.ca-selectable, matching theca-*class contract. Clean cutover:.selectablewas never documented outside a JSDoc line, and no stylesheet ever targeted it, so apps using it must rename it toca-selectable(right-click on a.selectableregion is now blocked again until they do).
Minor Changes
- 6e39edc:
caBrandPluginwritessrc/generated/brand.tsas soon as the Vite config resolves (the URLs depend only on a hash of the inputs, so no rendering is needed), not only at build start. Newcodeatlas-ui brandcommand andbrandUrls()/runBrand(): render the icons and the module without a Vite run, so a job that only runssvelte-checkon a fresh clone type-checks (svelte-kit syncdoes not load Vite plugins). - c2d255d:
FormSectionacceptsheadingLevel(2 | 3 | 4 | 5 | 6) andAppSettings/AppFormprovide the section heading level likePageShell, soFormSectionandEmptyStatetitles inside them render as<h2>under the page<h1>(axeheading-order) instead of<h5>. Outside a page layout the default stays<h5>. Only the tag changes, never the look. - af02ce2:
RadioGroup,Checkbox,SwitchandSelectacceptname, so their value is part ofFormDataon a native form submit (progressive enhancement, SvelteKit form actions without a hidden-input workaround).Selectrenders a hidden input carrying the selected value whennameis set. - e7b85e7:
ogCardacceptslang(BCP 47, default"en", escaped) for the card's<html lang>, so non-English cards get the right hyphenation and locale font selection. - 55ecc1a:
Box,Flex,StackandGridare generic onas:<Stack as="form" novalidate onsubmit={...}>accepts the form's attributes, and the handler'scurrentTargetis anHTMLFormElement(same foras="a",as="label",as="button"...). An attribute that does not belong to the tag is a type error. ExportsPolymorphicPropsandElementTag.
Patch Changes
- 153be87:
centsToDisplaykeeps the no-break spaceIntlputs after the currency symbol ("R$\u00a0123,45"), so a price no longer wraps between the symbol and the number.MoneyInputstill shows and parses a plain space in its editable text. - a517102:
toCsv,toJson,ExportColumnandExportMenuaccept any object row type (T extends object), so rows typed with aninterfaceno longer need a cast.keyis still checked againstkeyof T. - 8d25c2f:
caErrorPlugin({ scaffold: true })writes a validsrc/error.html: the theme init script now sits in<head>instead of inside<style>, the page uses the app theme (themeoption, same object ascaThemePlugin) instead of hard-coded amber, its copy andlangfollow the newlocaleoption (endefault, warns when unset), and thejavascript:history.back()link is gone (CSP-safe). The scaffolded+error.svelteuses tabs and double quotes like the other generated files.initnow writescaErrorPlugin({ scaffold: true, theme }). - 9e5655f:
initwithout--writeis now a true dry run: it printswould create <file>instead of writing new files. It also stops creatingsrc/lib/theme.ts(src/theme.tson Astro) whenvite.config.ts/hooks.server.tsalready importthemefrom elsewhere, and wires the files it adds to that import. - a7f7e96: Docs and agent skill: apps must install
@lucide/sveltethemselves. Under Bun's isolated linker the package's own dependency is not importable from the app. - 3ef5b11:
migratekeeps the default thickness ofGlassCardandGlassSurface: a usage withoutthickness(defaultregular) becomeselevation={3}instead of dropping it, so the migrated card keeps its fill, blur, shadow and rim (CardandSurfacedefault toelevation={1}). - 33ad505:
migratelooks uptypescriptin the project directory first (then next to the CLI) and printsmigrate needs typescript (bun add -d typescript)with exit code 1 when it is missing, instead of crashing withCannot find package 'typescript'. This also fixesbunx @codeatlasdev/ui migratein layouts where the CLI cache directory cannot see the project'stypescript. - b7cdd79:
migratenow ends with a list of the usages it left for you (file:line Component: reason (recipe)), and--strictexits with code 1 when any remain. AGridorStackwhosecols/aligncould not be mapped still gets itsgapconverted:gap={20}next tocols="2fr 1fr"becomesgap="5"instead of silently turning into step 20 (80px). Dynamic or off-scale gaps are reported, since their meaning changed from pixels to a step. - 98ea2ed: Nested modals: only the newest focus trap owns focus, so opening a
Dialog/AlertDialog/Sheet/Drawerover another no longer loops onfocusin(Maximum call stack size exceeded); closing it hands focus back to the one below. - daccdc8:
.ca-proseheading anchors stay visible on touch screens: a stray second@media (hover: none)block hid them withdisplay: none, contradicting the rule above it. New "Release lists" docs page documents theca-release*markup and the version-id anchor convention. - c0bc624:
--ca-scrim-blurnow drives the modal scrim (#135). It was declared asnonebut never read: the::backdropof Dialog, AlertDialog, Sheet, Drawer, CommandPalette and DocsSearch hardcodedblur(4px) saturate(180%). The scrim reads the token and the token defaults to that same value, so nothing changes visually;:root { --ca-scrim-blur: none }now removes the blur from all of them. The CommandPalette scrim shares it (it used a plainblur(4px), now with the sharedsaturate(180%)). - 5a3ede3: Agent skill and AGENTS.md import
ThemeOptionsfrom@codeatlasdev/ui/vite(/astroin Astro), like the docs andinit. The package root pulls.sveltefiles that Node cannot load fromvite.config. - 8185835: New
--ca-tooltip-blurtoken for the Tooltip surface blur (#137). It defaults tovar(--ca-blur-ultra-thin), so the look is unchanged;--ca-tooltip-blur: nonegives an opaque tooltip without re-tiering the shared ultra-thin blur (.glass-ultra-thin,.glass-btn-clear,--ca-upload-blur). - 3dda885:
@codeatlasdev/ui/viteexportsdesignStyle,DESIGN_STYLES,mergeThemeandDesignStyleId, so atheme.tsshared withvite.configcan use a design recipe (including the 3.0 migrationdesignStyle("liquid", { ... })) in plain Node. The theming guide and theinittheme comment now point to this entry instead of/astro.
3.1.0 latest
Minor Changes
- 0579bb3:
.ca-prosestyles release lists, so a changelog page needs no custom CSS (#126). Wrap one<section class="ca-release">per version (heading with its channel.ca-chip, then a.ca-release__metarow of date and links, then the rendered notes), add a.ca-release-jumppill index and close with.ca-release-thanks. Anchored releases clear a stickyDocsShellheader (scroll-margin-top), and the meta links and pills keep 44px touch targets. Classes only, no JS or component: the markup can come from Markdown rendered at build time or at the edge. - 973fe00: Add
SwatchGroup: a single-choice color swatch picker (role="radiogroup", roving focus, preset or customcolor). Its coarse-pointer hit area is the swatch itself, so neighbouring swatches never overlap and every click lands on the swatch the user aimed at.
Patch Changes
- b27c73a:
generateBrandand the brand Vite plugin no longer load every system font for each PNG they render. resvg did that by default on every construction (~450 ms per render on Windows, ~2.7 s per icon set, at every build and dev start); fonts now load only when the mark has<text>(#120). - 5c79b24:
FilterGroupdraws its label with--ca-text-secondaryinstead of--ca-text-hint. The hint role can be set low by a custom preset, and the label failed the 4.5:1 axe check on a raised surface in dark mode. - 5c79b24:
KpiCardis built onCardinstead of the deprecatedGlassCard, so rendering it no longer logs the[ca-ui]deprecation warning in dev. The look is unchanged (elevation 3).
3.0.0 latest
Major Changes
a1d14f5: Sober default theme, Liquid Glass is now opt-in (#111). The package used to look like a UI-kit demo: a glass material over a three-hue mesh, glow on selected states, a white rim on every surface, hue-tinted 24-40px shadows, 10px controls, 2px icons and 700 headings. The default is now solid and neutral:
defaultStyle: "solid",paper: "plain",radius: 0.8(--ca-radius-mdis 8px),iconStroke: 1.75, heading weight 600 (and no extra h1 tracking).- Surfaces, text and outlines carry almost no chroma (the brand hue stays on fills, focus, links and charts); shadows are neutral black (
--ca-shadow-*,--ca-glass-shadow,--ca-mat-*-shadow). [data-style="solid"]and[data-style="flat"]set--ca-glow-primary(-strong)and--ca-glass-rimto transparent and draw--ca-mat-edgeas a hairline, so.glass-tinted,.glass-prominent,.glass-glow,.glass-active,.glass-btn-prominentand the AppShell nav pills lose their halo and rim without a component change. The page mesh (--ca-page-mesh) is drawn only byglassandfrosted.- New recipe
designStyle("liquid")reproduces the former look exactly (glass, mesh, glow, round corners, bold headings, tinted shadows).designStyle("default")now means the sober default.
Migration: to keep the old look, use
designStyle("liquid", { ...yourTheme })(import it from@codeatlasdev/ui/vitein a theme shared withvite.config: the root and/kitcannot load in Node), or setdefaultStyle: "glass",paper: "mesh",radius: 1,iconStroke: 2andtypography: { weights: { heading: 700 } }.colorScheme.setStyle("glass")still switches at runtime. Apps that override--ca-*tokens are unaffected; apps that read--ca-radius-*or the shadow tokens get the new values.defineTheme()with no options now resolves the new defaults (designTokensemits nothing for them).
Minor Changes
8b36a1e: AppShellLayout: the shell keeps its state across page loads (#109). On an MPA (Astro, a full reload) every navigation is a new document, and the panel, the selected module, the expanded sections and the panel scroll all reset.
persistPanel(defaulttrue) now keeps the panel open/closed, the selected module and the section open map inlocalStorage["ca-shell"](the oldca-panel-openkey is migrated), and the panel scroll per module insessionStorage. An inline script emitted right after the sidebar restores them before the first paint and setsdata-ca-panel/data-ca-moduleon<html>, so a stored-closed panel is laid out closed with no flash, on the same model as the theme script. Stored values are validated and discarded when malformed. The user's last choice for a section wins overdefaultOpen; the section holding the current page always opens. The shell now starts at its target width instead of animating open on load.persistPanel={false}keeps the old always-default behavior for demos and embeds.3476d9a: Brand assets from one mark.
caBrandPluginandgenerateBrand(@codeatlasdev/ui/vite) render a content-hashed favicon (SVG that followsprefers-color-scheme, ICO, PNG),apple-touch-icon, 192/512/maskable manifest icons and a web app manifest from one SVG with{{ink}}/{{spark}}placeholders and your theme;resolveBrandandrenderMarkexpose the resolved sRGB colors.iconLinksandogCard(@codeatlasdev/ui/seo) emit the head links and the 1200x630 social-card HTML forrenderOgImage.<Seo>now also writesog:image:typeandtwitter:image.@resvg/resvg-jsandpng-to-icoare optional peer dependencies.1eeca1c: Surface engine and layout primitives (#112).
New components:
Box,Flex,Stack,Grid,Container,Section(layout),Surface,CardandSurfacePanel.- Layout primitives take token props (
gap,p/px/py,align,justify,direction,columns,wrap) and a polymorphicas. A prop renders as adata-*attribute (data-gap="4"), a responsive object ({ base: "2", md: "4" }) asdata-md-gap="4", styled bystyles/layout.cssin@layer ca.utilities. No inlinestyle. Surfacehas orthogonal axes:elevation0-5,materialauto | solid | frosted | glass | liquid,tone,variantfilled | outlined | ghost,radius,interactive,glow,as. One.ca-surfacerule reads--ca-surface-*variables.material="auto"follows<html data-style>; the explicit materials look the same under every style. It never setswill-changeorisolation, and it hasforced-colorsand reduced-transparency entries.Card(title,headingLevel,href,flush,full,headerActions) is built onSurface;SurfacePanelis a raised<section>(the AppShell sidebarPanelkeeps its name).- Docs: Surface, Card, Layout primitives;
docs/css-api.mdlists the newca-*classes and the data attributes.
Behavior changes to review in
Stack,GridandRow(they were the only layout components with inlinestyle):StackandGridgapis now a spacing step string (gap="4"is 16px), not pixels.align/justifyusestart | center | end | between | ...instead of raw CSS values.Gridcolsis nowcolumns(1-12, or a responsive object);rowGap,colGapand CSS template strings ("2fr 1fr",repeat(auto-fill, ...)) are gone: writegrid-template-columnsin a class.Rowis deprecated in favor ofFlex. It still renders.ca-rowand maps a pixelgapto the matching step.GlassCardandGlassSurfaceare deprecated in favor ofCardandSurface, and removed in the next major. They render the new components, keep theirca-glass-card*andglass*classes and look the same under everydata-style, and log a[ca-ui]warning in development.
Size:
styles/core.cssgrows from 9.6 to 13.0 kB gzip (limit 9.6 to 13.6 kB),global.cssfrom 12.6 to 15.9 kB:layout.cssis ~2.2 kB (every token prop and step at the base and four breakpoints, generated byscripts/build-layout.ts) andsurface.css~1.2 kB (the five materials by six elevations). JS entries are unchanged.Migration:
bunx @codeatlasdev/ui migrate --write srcrewritesGlassCardtoCard material="glass"(thicknesstoelevation,tintedtotone="brand",glowingtoglow, staticpaddingtop),GlassSurfacetoSurface(refractiontomaterial="liquid"),RowtoFlex, andStack/Gridpixel gaps,colsandalign/justifyvalues. Dynamic or off-scale usages are left as they are, with their import, for you to finish.- Layout primitives take token props (
49b2bf4: Unified layer engine. Dialog, AlertDialog, Sheet, Drawer, Lightbox, CommandPalette, DocsSearch, DropdownMenu, Popover, Tooltip, Select, Combobox, NotificationBell and the date pickers now share one layer stack (
primitives/layer.ts).- Modals are native
<dialog>+showModal(); menus, popovers and tooltips arepopoverelements. Both live in the top layer, so stacking no longer depends onz-index: a menu inside a dialog is always on top. The--ca-z-dropdownand--ca-z-tooltiptokens remain for your own in-flow UI but are no longer used by overlays. - Anchored layers use CSS anchor positioning when supported and Floating UI otherwise.
- One Escape / outside-press / focus stack: only the topmost layer reacts, so nested overlays dismiss in order. Focus returns to the trigger on close.
- New
onLayerChange(listener)(and alayerchangeCustomEventondocument) fires on every layer open and close with the open layers and their rectangles, so hosts can park or snapshot native views. - The
.ca-floatingwrapperdivis gone:.ca-floatingis now on the layer element itself (the one carryingdata-state/data-side), and.ca-date__floatwas removed.
- Modals are native
2.3.2 latest
Patch Changes
- 9b7015f: DocsShell: the sidebar keeps its place between pages. On a static docs site every sidebar link loads a new document, and the sidebar used to jump back to the top, hiding the page just picked (#99). The scroll position is now saved per section in
sessionStorage(on a sidebar click and onpagehide) and restored by an inline script emitted right after the sidebar, before the first paint; the current page is scrolled into the sidebar's view when it is outside it. Only the sidebar scrolls: the window and focus are untouched. Client-only mounts and pages whose CSP blocks inline scripts get the same restore on hydration.
2.3.1 latest
Patch Changes
3280fc2: Theme islands: a
data-theme-scopeinside adata-presetisland now takes that island's preset instead of the page's. Before, the page preset's rule also matched the scope, so a brand preset on<html>(unlayered) always won, and between two built-ins the one emitted later inglobal.csswon. The landing hero demo ignored its preset swatches because of this (#87).Bundle budget:
styles/global.css+122 B gzip (limit raised from 12800 to 12900 B). Each preset's scope selectors (light, dark and theirprefers-contrast: morecopies) now carry the nearer-island exclusion;core.cssstays within its limit.
2.3.0 latest
Minor Changes
1f858d3: The generated API reference now documents every non-component export of every entry point (root,
kit,vite,astro,actions,seo,forms,tokens,table,charts):setLocale,registerLocale,caErrorPlugin,createErrorHandler,nativeFeel,usePageTitle,primeKeyboard, the stores, theseobuilders and the types, each with its signature and a JSDoc summary.components.jsongains anexportsarray,API.mdgains a "Non-component exports" section, theca-ui-mcpserver gains aget_exporttool, and components ofkitsuch asRouteAnnouncernow appear in the component list. The summaries were added as JSDoc, so they also show in editor tooltips.648a1e6: New
.ca-sr-onlyutility (visually hidden, still in the accessibility tree) and.ca-sr-only--focusable(shown again while it or a descendant has focus, for skip links). Apps can now add a table<caption>or an AT-only label without custom CSS. Both live instyles/utilities.css(ca.utilitieslayer), socore.cssandglobal.csscarry them. Documented ingetting-started/accessibilityanddocs/css-api.md.The kit now uses it instead of eight copies of the clip pattern (
Dialog,Select,DocsSearch,Lightbox, the date pickers, and the page titles ofPageShell,AppForm,AppListandAppSettings; theTablecards-mode header row shares the same rule). Visual output is unchanged.Migration: four internal class names are gone because they only existed to hide text. If your CSS targeted
.ca-dialog__sr-only,.ca-sel__sr,.ca-docs-search__sror.ca-lightbox__sr, target.ca-sr-onlyinstead (these elements now carry that class). The page<h1>hidden insideAppShellLayoutbelow 900px now also carries.ca-sr-only--shell.Bundle budget:
styles/core.css+173 B gzip andstyles/global.css+206 B gzip (limits raised to the measured 9812 / 12800 B). The cost is the new public utility (the box, the focusable variant and two scoped copies that a class cannot express as one rule: the shell page title under a media query, the Table cards header under a container query); the eight component-level copies it replaces were already part of component CSS, not these entry files.19386b1: Add the
@codeatlasdev/ui/testingsubpath:expectNoA11yViolations,checkA11y,formatViolations,settledandA11Y_EXCEPTIONS. It is the library's own axe helper (animations settled,regiondisabled for isolated components), framework-agnostic (takes anElementorDocument), withaxe-coreas an optional peer dependency.a88b8a9:
EmptyStateandGlassCardacceptheadingLevel(2 | 3 | 4 | 5 | 6) to render their title as that heading without changing how it looks.EmptyStateinsidePageShellnow defaults to<h2>(it was always<h5>, which made axe reportheading-orderunder the page<h1>); outside aPageShellit stays<h5>.GlassCardrenderstitleas a<p>unlessheadingLevelis set, so existing outlines are unchanged. New exported typeHeadingLevel.5f7d842: New
KpiGrid: a responsive row ofKpiCards that never leaves an empty cell.Grid cols="repeat(auto-fill, minmax(160px, 1fr))"ignores how many cards it holds, so 3 cards on a 390px phone became 2 + 1 with a hole.KpiGridcounts its children in CSS (:has()quantity queries, no JavaScript, SSR-safe) and only uses column counts that divide that number: 3 cards are 3 across or one column, 4 are 4, then 2 x 2, then one column, 6 are 6, 3 + 3, 2 + 2 + 2. It follows its own width (container query), so it also works inside a sidebar layout. Above 12 children it falls back toauto-fill. Props:gap,class. New classes:ca-kpi-grid,ca-kpi-grid__grid.The docs, templates, demos and agent skill now use
KpiGridfor KPI rows instead of theGridauto-fill recipe.4db46ab: Three patterns for result views, from the DX feedback in #48.
LiveListshows rows that stream in (SSE, WebSocket, bulk jobs): a namedrole="log"with batched polite announcements instead of one per row, determinate or indeterminate progress, and auto-scroll that follows the newest row only while the user is at the bottom, pauses when they scroll up (with a "N new" jump button) and never moves focus.ExternalLinkis a link to another site withtarget="_blank",rel="noopener noreferrer", a decorative icon and a localised "(opens in a new tab)" for screen readers (externalLink.newTabin the i18n catalog), made for table cells.ExportMenuis an Export button with a CSV / JSONDropdownMenu; the helperstoCsv,toJson,downloadFileand theExportColumntype are exported too (RFC 4180, UTF-8 BOM in the downloaded CSV, spreadsheet formulas neutralised). New i18n keys:externalLink,exportMenu,liveList(en, pt-BR).11809ff:
MoneyInputacceptscurrency(ISO 4217; decimals come fromIntl, soJPYhas none andBHDhas three) andlocale(BCP 47).valueis now the currency's minor unit rather than always cents. Existing BRL usage is unchanged: with neither prop set the field still rendersR$ 1.234,56. Withcurrencyset and nolocale, the locale follows the active i18n locale (I18nProvider/setLocale).centsToDisplayandparseInputToCentstake an optional{ currency, locale }(defaultsBRL/pt-BR),parseInputToCentsreads the locale's decimal separator, andcurrencyDecimals(currency)and theMoneyOptionstype are exported from@codeatlasdev/ui/forms.
Patch Changes
- 5871641: AppShellLayout: the panel/main offset now uses the Svelte 5
Springclass and lands on the exact target (252px, not 251.984px). The extra CSSmargin-lefttransition that lagged the spring is gone, andprefers-reduced-motionskips the animation and jumps straight to the target. - ef7b0fa: CommandPalette: keyboard selection no longer gets stolen by the pointer. A row appearing under a stationary pointer (list renders, dialog animates in) fired
mouseenterand moved the highlight; hover now follows realmousemove. ArrowDown on an empty list no longer stores-1. - ee47b59: DocsSearch: keyboard selection no longer gets stolen by the pointer or reset by a lazy index. A row appearing under a stationary pointer (results render, dialog animates in) fired
mouseenterand moved the highlight; hover now follows realmousemove. A lazyindex()resolving while the dialog is open re-ran the open effect and wiped the typed query. ArrowDown on an empty list no longer stores-1. - 7f5d8f7:
initnow writes the full SvelteKit setup:caErrorPlugin({ scaffold: true })next tocaThemePlugin(theme)invite.config.ts, andhandleError = createErrorHandler()next to the themehandleinsrc/hooks.server.ts(both patched into existing files with--write, each only when missing; your ownhandleErroris never replaced). The installation guide shows one complete SvelteKit setup, the exact filesinitwrites, and states that plugin order inpluginsdoes not matter. - ea072c2: DropdownMenu, DataTable column toggle (and any
FloatingLayerconsumer): closing before the frame-delayed focus move ran no longer lets that move steal focus back from the trigger.FloatingLayer'sonMountedmay now return a cleanup that runs on close/unmount. - ba4c458:
PageShellactions below 900px: a lone icon-only control (IconButton,ThemeToggle) no longer stretches to the full row with its icon centred. Icon-only children (.ca-icon-btn,.ca-rail-btn) keep their natural size and sit at the end of the row; labeled buttons still share the row full-width as before.
2.2.0 latest
Minor Changes
d9087e8: Accessible tokens and states, solved per preset and scheme by the generator (every built-in and brand preset, light and dark):
- Disabled controls no longer fade with
opacity. Button (all variants, including the newdestructiveandglassdisabled looks), IconButton, Checkbox, Switch, Slider, Tabs, ToggleGroup, SegmentedControl, Pagination, PinInput, NumberField, RadioGroup, Select, Combobox, TextField, TextArea, date fields and the data-table controls draw the disabled state from--ca-text-disabled,--ca-action-disabled-bgand--ca-outline-default. A loading button keeps its enabled look. --ca-text-disabledis now guaranteed >= 3:1 on every surface and on the disabled fill, and documented (comment,tokens.jsondescription, docs) as the label of disabled controls only.lint:tokensflagscolor: var(--ca-text-disabled)outside a disabled selector.- New
--ca-outline-control(>= 3:1 on every surface and on the 5% field tint, WCAG 1.4.11).--ca-field-border-colordefaults to it, so TextField, TextArea, Select, Combobox, NumberField, PinInput and the date fields, plus the Checkbox and Radio boxes, reach 3:1.--ca-outline-subtlestays for dividers. Overridable like any--ca-*token. - New
--ca-semantic-error-contrast, the pair of--ca-semantic-error-fill.Button variant="destructive"reads it instead of a literal white.defineThemederives it from a customcolors.*.error. - Menus, popovers, pickers and panels are near-opaque by default (
--ca-overlay-bg/--ca-panel-bgare 94% of--ca-surface-overlay, the blur is kept), so label contrast no longer depends on the page behind. The old tints are--ca-overlay-bg-glassand--ca-panel-bg-glass. Thefrostedstyle keeps the opaque default. --ca-focus-ringis solved per preset and scheme (>= 3:1 on every surface and on the hover/selected fills) and every focusable component reads it (it was a literal--ca-brand-mainin about 20 components; the date fields now draw a real 2px ring instead of a 40% tint). New--ca-state-indicator(same guarantee) for the switch track, slider range and segmented thumb edge.lint:tokensrejects a literal--ca-brand-mainin a focus rule.prefers-contrast: moreandforced-colorskeep working: the contrast block also lifts--ca-outline-controland--ca-text-disabled.tokens.jsonentries now carry adescriptiontaken from the comment above the declaration.
Migration: apps that doubled the class to patch these (
.ca-btn.ca-btn:disabled,.ca-tf__wrap.ca-tf__wrap,.glass-overlay.glass-overlay,.ca-btn--destructive.ca-btn--destructive, a global:focus-visibleredraw) can delete those overrides.Bundle budget:
styles/core.css+61 B gzip andstyles/global.css+277 B gzip (limits raised to the measured 9639 / 12594 B). The cost is the three solved tokens (--ca-focus-ring,--ca-outline-control,--ca-semantic-error-contrast) and the twoprefers-contrast: morelifts in 16 preset blocks, which carry the guarantees above.- Disabled controls no longer fade with
d9087e8: Component CSS now lives in
@layer ca.components, declared last in the kit's layer order, so the documented contract is true: an unlayered app rule with the same single-class selector (.ca-btn:disabled,.ca-tf__wrap,.ca-tooltip-content,.ca-dialog__body) overrides the kit, with no doubled classes and no!important. Every component<style>block,Dialog/dialog.cssandDate/date.cssare wrapped, and each starts with the full@layerorder statement so bundler chunk order cannot demote the layer. The order incore.csschanged toca.reset, ca.tokens, ca.themes, ca.base, ca.materials, ca.utilities, ca.components. The redundant!importanton the error state of TextField, TextArea, NumberField and Combobox was removed (it would have beaten app CSS now that it sits in a layer). Behavior change: app rules that previously lost to a kit rule of equal or higher specificity now win; apps can delete their.ca-btn.ca-btn/!importantworkarounds. Layered app CSS (for example Tailwind@layer components) must be declared after the kit order statement to outrank it.d9087e8: i18n: open locales and request-scoped language (fixes #46).
Localeis nowstring(BCP 47).registerLocale(id, messages)adds any language with a partial dictionary; missing keys fall back to English, and region tags resolve to the base language (es-MX→es).getLocales()andgetMessages(locale, override?)are exported.- New
<I18nProvider locale messages?>: the language lives in Svelte context, so concurrent SSR requests never share it, and it stays reactive to prop changes.useLocale()anduseT()read it from components. setLocaleis now a no-op on the server (with a dev warning), because the module state it wrote was shared across every concurrent request. SSR apps must useI18nProvider; client-only apps keepsetLocale. A provider always wins oversetLocale.createErrorHandlerno longer reads global locale state. It takeslocale(a tag or(event) => tag) for the fallback message.
Migration:
- SSR (SvelteKit) apps: replace
setLocale(x)in the root layout with<I18nProvider locale={x}>…</I18nProvider>. Before, the server-rendered HTML used the locale of whichever request calledsetLocalelast. - Custom language via
setLocale("en", fullOverride): callregisterLocale("es", dictionary)and uselocale="es"/setLocale("es"). Date and number formatting can now follow the real locale. createErrorHandler(): addlocaleif the fallback message must not be English.- Removed export: the internal global
t()is gone (it was never exported from the package root). Library code usesconst t = useT()at component init.
Patch Changes
d9087e8: Fix the component descriptions in
components.json, theca-ui-mcpserver and the skill'sAPI.md: the manifest no longer takes the first JSDoc found in a component (usually a prop's, e.g.TextFieldread@deprecated Use description instead). Only a detached component-level doc comment counts, and tag-only comments are skipped.bunx @codeatlasdev/ui initnow supports SvelteKit 3: it reads the installed Kit version, never writes the removed$libimport (uses#lib/theme.tsor a relative path), and reports the changes it does not make for you (svelte.config.jsmoved intosveltekit({ ... }),tsconfig.jsonextending$app/tsconfig). The installation guide and the agent skill gain a SvelteKit 3 section and import the theme invite.config.tswith its.tsextension.d9087e8:
Select: an option whosevalueis""(e.g. "All projects") is now a real selection. The trigger shows that option's label instead of the placeholder and drops the placeholder styling, so filters no longer need a sentinel value. The placeholder shows only whenvalueisundefinedor matches no option.onchange("")was already delivered and is now covered by a test. Withrequired, a selected""option counts as a selection.
2.1.1 latest
Patch Changes
- 01b5188:
Steps: a leading<strong>/<b>step title is a block, so its line no longer mixes the bold face with the list item's regular-face strut. That mixed line rounded to a different height between page loads, shifting content below each step by 1px.
2.1.0 latest
Minor Changes
62d18f8: Theme: port a palette by role and pick the page paper, without raw
--ca-*overrides.PresetDefinition.colors: { light?, dark? }takes role colors (brand,background,sunken,surface,overlay,text*,border*,success/warning/info/error). A per-schemebrandregenerates that scheme's brand ramp;backgroundalso drives<meta name="theme-color">. Scoped to the preset, so islands and preset switching keep working.paper: "mesh" | "plain" | "grid" | "dots" | "lines"replaces the brand glow mesh with flat paper drawn in--ca-outline-subtle.- New
sketchbookdesign style: journal on grid paper, serif display, square outlined cards with hard shadows. - Opt-in
@codeatlasdev/ui/styles/marks.css: hand-drawn.ca-mark-underlineand.ca-mark-note(arrow), painted with--ca-mark-color. @codeatlasdev/ui/astrore-exportsdesignStyle,DESIGN_STYLESandmergeTheme, soastro.configcan use a recipe.
2.0.0 latest
Major Changes
dbb7193: bits-ui is gone. Every interactive component now runs on the library's own headless layer (WAI-ARIA Authoring Practices, platform first): native
<dialog>for Dialog, AlertDialog, Sheet, Drawer, Lightbox, CommandPalette and DocsSearch; native inputs for Checkbox, Switch and RadioGroup; own floating, dismiss, focus, roving-focus, listbox and presence primitives for Popover, Tooltip, DropdownMenu, Select, Combobox, Tabs, ToggleGroup, SegmentedControl, Slider, PinInput, Accordion, Collapsible, ScrollArea, Pagination, Avatar and the date family. Public props, classes anddata-stateattributes are unchanged.- Smaller bundles: AppShellLayout 41.2 → 35.1 kB gzip, form controls −18.7 kB, date family −45.2 kB.
- No more
ssr.noExternalfor bits-ui and no WKWebView dialog workaround needed. Select/Comboboxwithrequiredblock native form submit while empty and focus the trigger.- Disabled
Slidernow renders dimmed (the selector never matched before). - Interaction patterns were designed after studying Bits UI (credited in NOTICE).
Breaking (upgrade notes)
Props, exported types,
ca-*classes anddata-stateattributes did not change. What changed is the DOM around overlays and the dependency set:- Modal overlays (Dialog, AlertDialog, Sheet, Drawer, Lightbox, CommandPalette, DocsSearch) are a single native
<dialog>moved to<body>; the scrim is its::backdrop. CSS that targeted a separate overlay/scrim element or the old portal container no longer matches. Styledialog.ca-overlay::backdropinstead. --bits-*CSS variables anddata-bits-*attributes are gone (--ca-anchor-width,--ca-accordion-height,--ca-collapsible-height,data-ca-dayreplace the ones used internally).bits-uiis no longer installed with the package. If your app imports frombits-uidirectly, add it to your own dependencies.- Select no longer renders bits-ui's hidden
<input>;requiredstill blocks native form submit.
1.2.0 latest
Minor Changes
e5b0fe8: Design tokens v2: every visual dimension is a theme knob, so one component set can render any design language.
- New
defineThemeoptions:controlRadius, numeric or nameddensity(compact/comfortable/airy),border,typography(scale, weights, heading/label tracking and case, line height, measure),elevation(soft/none/hard/glow),motion(scale, easing personality),iconStroke,noise.designTokens()exposes the mapping. - Scales are parametric: spacing = 4px × step ×
--ca-density(new half steps 0.5/1.5/2.5/3.5 and 20/24), radius ×--ca-radius-scale, type ×--ca-font-scale, durations ×--ca-motion-scale(0 under reduced motion).[data-ca-tokens]re-derives them inside an island. - Fluid layout tokens:
--ca-gutter,--ca-section-gap,--ca-content-max, fluid display sizes--ca-font-size-display-1..3. Role tokens:--ca-radius-control,--ca-control-height*,--ca-border-width*,--ca-border-color,--ca-font-weight-*,--ca-heading-*,--ca-label-*,--ca-line-height-*,--ca-measure,--ca-icon-stroke,--ca-noise-opacity,--ca-ease-emphasized. - Material tiers read
--ca-mat-{tier}-{filter,fill,shadow}and--ca-mat-edge. New surface styles:outlined,neumorphic,clay,tonal(alongside glass, frosted, solid, flat). - Design style recipes:
designStyle(id, overrides)andDESIGN_STYLESfor Swiss, Neobrutalism, Neumorphism, Glassmorphism, Material 3, Fluent 2, Apple HIG, Bento, Claymorphism, Y2K, Editorial and Terminal. - Headings read the theme's display family, heading weight, tracking and case; Lucide icons read
--ca-icon-stroke. - One breakpoint scale (600/900/1200/1536) everywhere, enforced by the token lint, which also rejects malformed
svelte-ignorecomments.
- New
e5b0fe8: RadioGroup:
variant="cards"(selectable cards with label anddescription, whole card is the hit area) andaria-label. Option ids are now unique per instance, so two groups with the same values on one page no longer share label targets. CodeTabs: a plaincodetab renders inside the framed.ca-codeblock as a focusable scroll region.e5b0fe8: Remove the legacy theme editor generator:
editorTokensToCSS,editorPreviewCSS,DEFAULT_CONFIGand theThemeConfigtype are gone, with no aliases.Replacement:
defineTheme()+themeCSS()(ordesignStyle(id, overrides)for a recipe) cover every dimension the old hue/chroma/radius/spacing/shadow config exposed, and[data-ca-tokens]+[data-style]+[data-theme-scope]scope a theme to a single element. The theme editor at /theme/ is rebuilt on them: it starts from a design style recipe, edits anyThemeDefinitionkey and prints the exactdefineTheme({ ... })call to paste.Migrate:
ThemeConfig.hue/chroma/lightL→presets: [{ id, color }];radiusBase→radius;spacingBase/paddings →density;fontSans/fontMono→fonts;shadowIntensity→elevation;surfaceStyle→defaultStyle.e5b0fe8: Viewport hardening:
--ca-vh-min(small viewport,svhwithvhfallback) for full-screen sections, so a hero never hides its call to action behind the browser UI.- Focus is never obscured by sticky chrome:
scroll-padding-blockon:rootaccounts for the header, safe areas and--ca-sticky-top/--ca-sticky-bottom. - Prose heading anchors are always visible on touch screens (no hover-only affordance).
- BottomNav labels use an 11px floor.
- SimpleTable
responsive="cards"captions every cell with its column header automatically;data-labelonly overrides. caThemeHandlewarns in dev when the viewport meta blocks zoom (user-scalable=no,maximum-scale=1).
1.1.0 latest
Minor Changes
f7d9a25: Accessibility foundations.
- New
SkipLinkcomponent (ca-skip-link).AppShellLayout,DocsShellandAuthLayoutrender it as the first tab stop and own the#maintarget.DocsShellkeeps itsca-docs-skipclass anddocs.skipToContentlabel, but its<main>id is nowmain(wasca-docs-content): update any CSS or links that targeted#ca-docs-content.AppShellLayoutgetsmainId(default"main"; passnullwhen your page renders its own<main id="main" tabindex="-1">) andskipLink(setfalsewhen the shell is embedded in a page that already has one). - New
announce(message, politeness)backed by one shared live region.CopyButtonnow announces through it (its localca-copy__statuselement is gone), andButton/IconButtonannounce "Finished loading" whenloadingends. NewRouteAnnouncerin@codeatlasdev/ui/kitannounces SvelteKit navigations (document title, or the page<h1>). forced-colors: activesupport: glass, blur and shadows are removed, surfaces get aCanvasTextborder and focus usesHighlight.prefers-contrast: moretokens (text-secondary and text-hint at 7:1 or more, outlines at 3:1 or more) are emitted per preset by the theme generator, so built-in and brand presets fromdefineThemeboth get them. Built-in theme CSS is regenerated.Comboboxgets anariaLabelprop to name the control when there is no visiblelabel.
- New
fa63ddd: Developer and agent tooling.
ca-ui-mcp: a local MCP server (stdio) withlist_components,get_component,search_tokensandget_guide, backed by manifests generated from the component source. Works offline.components.jsonandtokens.jsonare generated on every build and exported as@codeatlasdev/ui/components.jsonand@codeatlasdev/ui/tokens.json(props, types, defaults, snippets, publicca-*classes,--ca-*tokens).codeatlas-uiCLI.initwires the theme, hook or integration,global.css,colorScheme.init()and the mobile viewport into a SvelteKit or Astro app without overwriting existing files (it prints a diff;--writeapplies it).migrateis a codemod that renamesBrandPresettoPresetDefinition.- Dev-only
[ca-ui]warnings forIconButtonwithout an accessible name andDialogwithout atitle.
0128aee: Performance foundations.
- New
Imagecomponent: requiredwidth/height(no layout shift), lazy by default,priorityfor the LCP image (eager,fetchpriority="high", preload link),decorativefor presentational images, dev warnings for missing dimensions andalt=""withoutdecorative. defineTheme({ fonts })accepts font descriptors (family,src,weight,display,preload,fallback,fallbackMetrics): the theme CSS emits@font-faceplus asize-adjustfallback face, andthemeHead/caThemeHandleemit<link rel="preload" as="font" crossorigin>. Plain stack strings keep working. New exported typesFontDescriptor,FontFallbackMetrics,FontSpec.- New stylesheet entries
styles/core.css(everything plus the default amber preset) andstyles/presets/<id>.css(one built-in preset each).styles/global.cssstill imports everything. - Internal: bundle-size budget (
bun run size), token lint (lint:tokens), both part ofvalidate. Built-in preset sources moved fromstyles/themes/tostyles/presets/(not a published path).
- New
7abc0f9: Add the
@codeatlasdev/ui/seoentry point: aSeohead component (title template, canonical, robots, hreflang, Open Graph, Twitter, JSON-LD with<escaped), typed JSON-LD builders (organization,websitewithSearchAction,softwareApplication,product,faqPage,breadcrumbList,article,graph),sitemapXmlandrobotsTxt,llmsTxtand amarkdownNegotiationSvelteKit handle for AI crawlers,ogImageRoutefor 1200x630 social images through an injected headless browser, and PWA helpersmanifest()andappleMeta().caThemeHandlenow warns in development when the final HTML has noviewport-fit=coveror nolangon<html>.esm-envis now a direct dependency (dev-only warnings).
Patch Changes
- 7bcfd57: Browser chrome color sync no longer gets stuck when the page starts in the background (or WebKit pauses its frames): a timeout backs up
requestAnimationFrame, and the color re-syncs when the tab becomes visible. - cd94a19: Browser chrome color sync re-samples after CSS transitions end, so the status bar no longer keeps an in-between color when a theme switch animates the header background.
- c9099ba:
CommandPalette: pressing Enter on a result closes the palette again with bits-ui 2.19. Closing returns focus to the element that opened it while the Enter key is still down, and the browser then activated that element and reopened the palette; the palette now closes onkeyup. Also builds with@sveltejs/package3 and TypeScript 6.
1.0.0 latest
Major Changes
ef015e6: 1.0.0: stable API. From here on, breaking changes to props, exports,
--ca-*tokens or the publicca-*classes (seedocs/css-api.md) only land in a major.Breaking
- Default UI strings are English and come from a typed catalog (
en,pt-BR). Portuguese apps callsetLocale("pt-BR")once in the root layout; the wording is the same as before. Text props (label,placeholder,confirmLabel,emptyText, …) no longer have hardcoded defaults: omit them to use the catalog, pass them to override. ThemeOptionsis nowPartial<ThemeDefinition>(defineTheme).BrandPresetis removed: usePresetDefinition({ id, color, label? }).brandPresetCSSoutput changed (theme islands) and the head style id isca-theme(wasca-brand-presets).- CSS is layered:
global.cssdeclares@layer ca.reset, ca.tokens, ca.themes, ca.base, ca.components, ca.materials, ca.utilities. App CSS stays unlayered and wins without!important; override a token by declaring--ca-*on:root. - Page background:
htmlpaints a solid--ca-surface-baseand the decorative mesh moved tobody::before(Safari 26 and the overscroll area sample the root color).bodyis transparent. MobileHeaderandBottomNavare opaque (--ca-chrome-surface) so the browser status bar and toolbar match them.autofocuson touch devices only focuses when the keyboard was primed in the opening tap (primeKeyboard()); pass{ touch: true }to force. This removes the iOS "focus without keyboard" jump.responsiveisundefined-width untilinit()runs (no hydration mismatch); derived values treat it as desktop.- The
.ca-app-scoped 16px input rule and the.ca-input-ios-safeclass are gone: every text field is covered globally.
Theme engine
defineTheme()+themeCSS(): typed theme object compiled once to deterministic CSS: brand presets from one hex, fonts, radius multiplier, density, raw token overrides.- Built-in presets are generated from
theme/builtins.ts(bun run --cwd packages/ui themes;validatefails ifstyles/themes/*.cssis stale). - The init script validates stored preset/scheme/style against what the app knows (an old
ca-presetno longer repaints the app in amber), appliesdata-stylebefore first paint, sets<meta name="theme-color">, follows the OS undersystembefore hydration and replays runtime tokens. - Theme islands:
data-theme-scope="light" | "dark"anddata-preseton any element, nested in any order. colorScheme.setTokens(vars)writes one<style id="ca-runtime-tokens">, persisted and replayed before paint.colorScheme.init()is idempotent and returns a cleanup; storage errors (Safari private mode) no longer throw.colorScheme.presetslists valid ids.
Mobile and WebKit
- Tokens
--ca-safe-top|right|bottom|left,--ca-vh(dvh with vh fallback),--ca-kb-inset,--ca-input-font-size(-sm),--ca-chrome-surface,--ca-bottom-nav-h. viewportstore (on-screen keyboard inset fromvisualViewport, ignores URL-bar and pinch-zoom changes) and thekeyboardAwareaction.primeKeyboard()/focusTextField()(from@codeatlasdev/ui/actions): open the iOS keyboard from the tap that opens a dialog, sheet or search. Used byDocsSearch,CommandPaletteandCombobox.- Browser chrome color sync:
theme-colorfollows the opaque color under the top edge (header, theme island or page) on Chrome/Samsung Android, Safari ≤ 18 and PWAs. - New components:
StickyActions,MobilePane.Sheetswipe-to-dismiss (swipeToDismiss),AuthLayoutmobileBrand. - 44px touch targets for every
Buttonsize, press feedback on touch, no tap flash, 16px minimum on every text field (no iOS zoom),-webkit-text-size-adjust. - Overlays contain overscroll;
backdrop-filterfalls back to opaque surfaces where unsupported. nativeFeel()(kit): keyboard-only focus rings, no browser context menu or selection on chrome,.ca-selectableopt-in, optional zoom lock for desktop shells;html[data-ca-surface="transparent"]for vibrancy windows.
Integrations
- Astro:
caUI(theme)from@codeatlasdev/ui/astroinjects the theme before first paint and configures SSR. CodeTabsacceptstabsas data (for MDX/Astro where Svelte context cannot reach children);CodeSnippetand code tabs follow the scheme of their context through dual shiki themes.
Fixes
- Glass lost its blur in production builds: Lightning CSS (Vite 8) drops
backdrop-filterdeclared before-webkit-backdrop-filter. Order fixed everywhere and guarded by a test. Dialogbody no longer collapses to zero height in WKWebView (Tauri, Safari).Selectno longer putsaria-requiredon the trigger button; required state goes to the form control and the label.isItemActivehandles trailing slashes (trailingSlash: "always"sites).Tooltipmeets AA contrast in both schemes (--ca-tooltip-bg).- New tokens
--ca-focus-ring,--ca-focus-ring-offset,--ca-selection-bg,--ca-scrollbar-thumb(-hover); reduced motion zeroes--ca-duration-*.
- Default UI strings are English and come from a typed catalog (
0.7.1 latest
Patch Changes
- Charts now re-theme on a color scheme, preset or surface style change. Their options read design tokens from CSS custom properties, which aren't reactive, so a theme switch re-applied the previous theme's axis, text and palette colors. Token reads now track the theme store, so every chart option rebuilds with the new values.
0.7.0 latest
Minor Changes
Accessibility and behavior fixes found by the new test suite.
- New tokens
--ca-text-hint,--ca-brand-textand--ca-semantic-error-fill(WCAG AA contrast). Hints, placeholders and brand-colored text now use them. DropdownMenu,PopoverandTooltipaccept atriggerChildsnippet that receives the trigger props, so aButtoncan be the trigger without nesting interactive elements.ButtonandIconButtonforward extra attributes.CheckboxandSwitchacceptaria-labelandaria-labelledby.Switchis now 44×24 px.- Fixed: Combobox popup position and keyboard focus; MoneyInput lost typed input;
use:maskfiredinputtwice per keystroke; DataTablearia-sort, page reset and column toggle;colorScheme.resolveddid not follow OS changes insystemmode; calendar navigation labels in pt-BR; DatePicker popover focus; Toaster, NotificationBell, Select, FileUpload, ScrollArea and SettingItem ARIA issues. - CodeSnippet uses the high-contrast GitHub shiki themes.
DocsSearchresults are real links again (<a role="option" href>), so middle-click, open in new tab and copy link work. A plain click still goes throughonselect.CommandPalettegainsminQueryLength(default1). The query is now trimmed before matching, so" rel "finds "Relatórios".
- New tokens
0.6.0 latest
Minor Changes
Docs primitives for developer documentation sites, mobile-first.
DocsShell: page frame with sticky glass header (brand, section tabs, search, theme), sidebar + "Nesta página" rail on desktop, and ONE compact header on phones (safe-area insets, 44px targets) whose menu opens a bottomSheetwith a section switcher and the sidebar tree. The page index becomes an inline disclosure on small screens. ⌘K / Ctrl+K //open search; skip link included. Props:sections,active,groups,pathname,headings,searchIndex,onnavigate,brand,actions,menuFooter.DocsNav(sidebar tree withMethodBadge),TableOfContents(variant="rail" | "inline", scrollspy),DocsSearch(dialog on desktop, full-height sheet with a 16px input on phones; accepts entries or an async loader) and thesearchDocsranking helper.CodeTabs/CodeTab: language tabs with keyboard support, 44px targets, scrollable strip, copy button, and preference sync across groups (synckey, persisted in localStorage; first paint always renders the first tab so SSR hydrates cleanly).Callout(note, tip, caution, danger; built onAlert),Steps,LinkCard,CardGrid,MethodBadge..ca-prosetypography inglobal.css: headings with balanced wrapping, links, inline code that wraps, tables in.ca-table-wrap(scrollable, focusable), shiki dual-theme code blocks in.ca-code, tight mobile spacing, reduced motion.- Showcase: Navigation / Docs.
0.5.2 latest
Patch Changes
- AppShellLayout: skip the desktop panel for modules with a single destination, so the rail item, panel entry and page title no longer repeat the same label.
0.5.1 latest
Patch Changes
Mobile polish.
- Inputs no longer trigger Safari's focus zoom: TextField, TextArea, Select, NumberField, Combobox, CommandPalette and the DataTable search/page-size fields use
max(16px, var(--ca-font-size-body2))under(pointer: coarse), (max-width: 767px).base.cssadds the same rule for rawinput, textarea, selectinside.ca-appand a.ca-input-ios-safehelper class. Zoom itself is never disabled. - New
Sheetcomponent: bottom sheet (bind:open,title,description,onclose,children,footer) with drag handle, safe-area padding, focus trap, Escape/backdrop close and reduced-motion support; centered dialog from 768px. AppShellLayoutgainsmobileTitlesnippet that replaces the plain page-title text in the mobile header (tappable session title, etc.).AppList,AppSettings,AppForm,PageShell: insideAppShellLayoutthe h1 is visually hidden below 900px (kept for a11y) because the mobile header already shows the title; below 768px the actions wrap under the description, full-width.EmptyStategainsvariant="hero": larger brand-tinted icon tile with glow, bigger title, actions stacked full-width on mobile.- Button: disabled
filledkeeps the brand hue (AA fill + contrast text at 0.45 opacity) instead of looking muddy. AppSettings(mobile): tabs in the horizontal strip were inheritingwidth: 100%and each filled the row; they now size to content, with a 36px touch target, scroll-snap and a trailing edge fade.AppList/useListState: newofLabeloption (default"of") for the word in the auto count and page info; pass"de"for pt-BR ("5 de 5"). On mobile the auto search field now fills the toolbar instead of keeping its 180px minimum.
- Inputs no longer trigger Safari's focus zoom: TextField, TextArea, Select, NumberField, Combobox, CommandPalette and the DataTable search/page-size fields use
0.5.0 latest
Minor Changes
AppShell (mobile): one compact sticky header. New
mobileHeaderActionssnippet onAppShellLayoutrenders at the header's trailing edge (switchers, account menu), so apps no longer need a second bar under the header. The header pads forenv(safe-area-inset-top);BottomNavextends its background throughenv(safe-area-inset-bottom)over an opaque base (no dark band above Safari's toolbar), uses equal shrinkable columns with ellipsised 10px labels so five items fit at 320px, and has 44px touch targets. Apps must setviewport-fit=coverin their viewport meta for the insets to apply.Theme: new
--ca-brand-fill/--ca-brand-fill-hovertokens in every static theme and in generated brand presets.--ca-brand-contrasttext on--ca-brand-fillis at least WCAG AA (4.5:1): white is preferred, the fill is darkened (hue and chroma kept) when needed, and ink text is used only for very light brands.generatePresetnow picks the text colour by real contrast instead of an L threshold; Button/IconButton filled, Badge brand, active Tabs, Pagination, Checkbox, Stepper and the nav badge now use the fill tokens. Disabled filled Button keeps the AA pair at 0.6 opacity.New
Lightboxcomponent: full-screen image viewer (bind:open,src,alt,onclose) with focus trap/restore, Escape, backdrop and close-button dismissal, safe-area insets and reduced-motion support.
0.4.4 latest
Patch Changes
Dark themes:
--ca-brand-contrastis now white. Text and icons on brand fills (primary Button, IconButton, Badge, active Tabs, Checkbox, Pagination, Stepper, PanelNavItem) were near-black on the dark presets. APCA contrast on the dark brand fills: white Lc 70–84 vs near-black Lc 31–45 on every preset (WCAG 2 ratios favor black on crimson/cyan/emerald/ocean/slate, a known WCAG 2 error for mid-tone fills in dark mode).Dialog: new
size="xl"(820px) for two-pane layouts such as settings.Kbd: flat keycap (tinted fill, heavier bottom edge) instead of a glass blur, which rendered as a near-black block on dark surfaces.
0.4.3 latest
Patch Changes
- SvelteKit 3 support:
useBreadcrumbsandusePageTitleread$app/state(the removed$app/storesbroke Kit 3 apps at import), andcaThemeHandlederives itsHandletype from@sveltejs/kit/hooksso it type-checks on Kit 2 and 3. The@sveltejs/kitpeer range is now^2.12.0 || ^3.0.0. Hook return types are unchanged (Readable).
0.4.2 latest
Patch Changes
MobileHeaderfills the width of the mobile shell bar instead of shrinking to its title (the bar background covered only part of the screen).- Tints no longer drift toward purple/pink. Every
color-mixof a token with a neutral (gray tokens such as--ca-glass-tint, black, white) now mixes in OKLab: in OKLCH an achromatic color has hue 0°, so the hue was interpolated between the brand hue and 0°. SelectedChip, destructiveButton/DropdownMenuItemhovers and other tinted surfaces now keep the brand or semantic hue.
0.4.1 latest
Patch Changes
Drawer: overlay no longer renders above the panel. Depending on CSS load order the base.ca-overlayz-index (modal) won over the drawer one and blurred the open drawer.
0.4.0 latest
Minor Changes
Brand themes and brand slot.
caThemeHandle(options)andcaThemePlugin(options)acceptThemeOptions:defaultPreset,defaultSchemeandpresets, brand presets generated from one hex color and inlined in<head>. Before this, the init script always fell back toamber/darkand overrodedata-presetset inapp.html.colorSchemestarts from the configured defaults instead of hardcodedamber/dark.PresetIdaccepts custom ids;BuiltinPresetIdlists the eight shipped presets.- Charts use the brand hex as the first series color for brand presets (they fell back to amber).
hexToOklchandbrandPresetCSS/themeHead/themeScriptexported for custom setups.tokensToCSSdark block now includes the neutral outline/glass/scrim tokens, so generated presets no longer inherit light-mode outlines in dark mode.AuthLayoutpanel mixes brand with neutrals in OKLab: in OKLCH the hue swung toward 0° (blue brands rendered purple).AuthLayoutbrand panel now hides below 768px (the override lost to the scoped rule);AuthCardfooter links use the brand color.ThemeToggle: light/dark button for the AppShell rail, SSR-safe icon.brandsnippet for the logo inAppShellLayout(top of the Rail and mobile header;MobileHeadergainsleading) and inAuthLayout. Both default to the previous "CA" text mark.
0.3.0 latest
Minor Changes
Breaking: charts, DataTable and CodeSnippet moved out of the root import into subpaths with optional peers, so the core install no longer pulls ECharts, TanStack Table or Shiki.
import from install @codeatlasdev/ui/chartsecharts@codeatlasdev/ui/table@tanstack/svelte-table@codeatlasdev/ui/codeshikiMigrate: change
import { LineChart } from '@codeatlasdev/ui'to'@codeatlasdev/ui/charts'(same forDataTable*,Table,createCaTable,createCaColumnHelper,caTableFeatures,PaginationState,SortingState,Updater→/table, andCodeSnippet→/code), then install the matching peer.SimpleTableandUserCellstay in the root.
0.2.2 latest
Patch Changes
- New README: AI-first quick start with
npx skills add codeatlasdev/ui.
0.2.1 latest
Patch Changes
- Charts load a modular ECharts build with only the series and components they use. The lazy chart chunk drops from 1,124 kB to about 650 kB (374 to about 218 kB gzip).
0.2.0 latest
Minor Changes
- Package exports now point to
dist(fixes@codeatlasdev/ui/vitefailing to load fromnode_modules). Adapted to the bits-ui 2 API (Accordiondropscollapsible), swapped deprecatedlucide-sveltefor@lucide/svelte, and exported the sharedIconComponent/IconPropstypes plus TanStackPaginationState,SortingStateandUpdater. The theme plugin no longer warns about FOUC whencaThemeHandleis set inhooks.server.
All notable changes to @codeatlasdev/ui are documented here.
Format based on Keep a Changelog. Versioning follows Semantic Versioning.
0.1.0 2026-09-26
Added
- 60+ components across page templates, navigation, data, forms, charts, overlays, and feedback
- Page templates:
AppList,AppForm,AppSettings,PageShell: complete screen patterns in one component - Navigation shell:
AppShellLayoutwithRail,Panel,BottomNav,MobileHeader,SiteHeader - Layout primitives:
Grid,Row,Stack: replace manual flex/grid entirely - Charts (8):
LineChart,BarChart,PieChart,ScatterChart,HeatmapChart,GaugeChart,RadarChart,Sparkline - Data:
DataTable(TanStack Table v9),SimpleTable,Pagination,KpiCard,EmptyState,FilterGroup - Forms (11):
Button,TextField,TextArea,NumberField,Select,Combobox,Checkbox,RadioGroup,Switch,Slider,FileUpload - Auth:
AuthLayout,AuthCard - Overlays:
Dialog,Drawer,Popover,Tooltip,DropdownMenu,CommandPalette - Feedback:
Toast,Spinner,Skeleton,Progress,ErrorPage - 8 built-in OKLCH themes: amber, ocean, crimson, cyan, violet, emerald, rose, slate: set via
data-presetattribute - Liquid Glass material system:
GlassCard,GlassSurface,GlassAppBar,LiquidGlass - CSS token system:
--ca-*custom properties for color, spacing, radius, typography, z-index - SvelteKit helpers (
@codeatlasdev/ui/kit): theme handle, breadcrumbs, page title, error templates - Vite plugin (
@codeatlasdev/ui/vite): fixes SSR externalization for@tanstack/svelte-table - Dark mode: independent from themes, compose freely (
data-preset="ocean" class="dark") - Svelte 5 runes throughout:
$state,$derived,$props,$effect, no legacy APIs .agents/skills/: AI agent skills for codeatlas-ui, DX, and writing