Skip to content

@codeatlasdev/ui@4.6.0

Latest

Choose a tag to compare

@github-actions github-actions released this 25 Oct 22:41
· 9 commits to main since this release

Minor Changes

  • 0219812: AI agents get one canonical entry point. llms.txt opens 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_guide and the new per-page files /llms/<slug>.txt carry the complete demo source of each page, while llms-full.txt stays under its old size. ca-ui-mcp adds get_recipe and reports deprecated components (Row, GlassCard, GlassSurface) with their replacement. init writes a marked, idempotent block into the app's AGENTS.md. The agent skill no longer teaches the removed cols/pixel-gap layout API or deprecated components.

  • 1b1b993: AspectRatio (#446, part of #432): a box that holds ratio for a video, an embed or any child with no size of its own; a direct img, video, iframe, canvas or svg child fills it. Image gains ratio (crop to a width / height box, filling the container) and fit (cover or contain). Replaces the Radix AspectRatio row of the React equivalence table.

  • 23fa9ee: Auth building blocks (#379): Turnstile in @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:handle with getToken() / reset(), removed on unmount, silent no-op without a siteKey), PasswordStrength (3-step meter with a polite text label, pure passwordScore() and a score / labels override), SplashScreen (role="status", ready + minDuration, skipped under reduced motion) and Avatar decorative (no accessible name next to a visible name). New i18n groups passwordStrength, splashScreen and turnstile (en, pt-BR).

  • 00757fe: AuthLayout: panelTone (auto | brand | flat) and the --ca-auth-panel-bg / --ca-auth-panel-fg tokens. A flat panel is a solid surface with a hairline, themed text and no blobs; the default auto picks it under the flat and outlined surface styles, so themes that forbid gradients no longer override .ca-auth__panel and .ca-auth__blobs from 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 (md 900px or lg 1200px; the panel snippet is unmounted while hidden, so media inside is never fetched on mobile), a header snippet (sticky bar over the form column that replaces the mobile brand strip) and align="start". AuthCard gets variant="plain" (display-size title, no card width cap). New template: Waitlist with map (#347).

  • b192b0a: Badge renders an inline status pill: <Badge tone="danger" subtle dot size="md">Overdue</Badge> (tones brand, success, danger, warning, info, neutral; subtle is a tinted background whose label clears 4.5:1 in light and dark). Button gains trailingHint (a Kbd in trailing is hidden below md and on touch with no reserved gap) and align="between" (label at the start, trailing at the end; the spinner keeps its place). Defaults are unchanged.

  • acdf4f3: ButtonGroup (#448, part of #432): attaches Button, IconButton and a DropdownMenu trigger into one role="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: Button and IconButton get tone="neutral" (#357): outlined and ghost buttons drawn with a neutral label, border and hover/pressed fills instead of the brand colour, for secondary actions beside a brand filled one. IconButton also gets the outlined variant. Label and border contrast is checked for every preset in tests/contrast.test.ts.

  • 61912be: Carousel and CarouselItem (#445, part of #432): native scroll-snap slides that follow the WAI-ARIA carousel pattern (labelled region, slides named "n of m", off-screen slides inert, labelled dot buttons with aria-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 under prefers-reduced-motion. No carousel engine dependency. Replaces shadcn Carousel. New i18n group carousel (en, pt-BR).

  • 82edb9d: ca-ui-mcp gains get_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 reads dist/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 real radiogroup of native radios (roving tab stop, arrows/Home/End/Space/Enter, disabled items skipped), three columns below sm and free wrap from sm, a check icon on the selected chip, error as role="alert" with aria-invalid/aria-describedby on the group, name/required for native forms, 44px chips. Use it instead of RadioGroup (too tall) or SegmentedControl (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 pre and a bare Markdown pre inside .ca-prose use 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). The code inside a CodeSnippet also pins --ca-font-mono now: it used the UA's bare monospace, 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, IconButton and Kbd. 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 new ThemeOptions.components (button, iconButton, kbd, merged by mergeTheme). 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-btn or .ca-kbd.

  • 3b76318: ca-ui-mcp gains list_concepts and get_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 a DropdownMenu at 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. The menu snippet takes the same items as DropdownMenu (items, checkbox and radio items, submenus, labels, separators). Text fields keep the browser's menu; disabled keeps it everywhere; bind:open, onOpenChange, focusable, menuClass.

  • c19ba2b: Density and appearance tokens for Avatar, TextArea, TextField, Card, SegmentedControl and EmptyState, on the same --ca-<component>-<property> convention as the Button tokens, also settable through ThemeOptions.components. Avatar gets tone="neutral" (surface background, secondary text, hairline ring) and --ca-avatar-fallback-{bg,color,size,weight} with per-size --ca-avatar-<size>-fallback-*. TextField and TextArea read --ca-field-gap and --ca-field-label-leading (or their own --ca-tf-* and --ca-ta-*); --ca-ta-min-height is the height of the visible box, border included. Card reads --ca-card-{bg,radius,border,shadow}. SegmentedControl reads --ca-seg-item-{padding-x,font-size,line-height} (and per size). EmptyState gets bare, actionsFill and --ca-empty-{padding,title-size,title-weight}. Unset, every component draws what it drew in 4.5.0.

  • ab42958: ca-ui-mcp gains search_terms, get_term and get_reference: the 269-term design atlas and the verified sources behind it, offline from dist/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>/ for swiss, editorial, brutalism, sketchbook, glassmorphism and terminal, 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, the designStyle() recipe and a prompt. The ca-ui-mcp server gains get_style (a style as markdown, or the digest of all of them) and llms.txt links 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: Dialog split is configurable: splitSize={{ width, height }} (any CSS length), splitColumns="2fr 3fr" and fullscreenBelow="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-height and --ca-dialog-split-columns. Defaults are unchanged. The insertion-line-free column drop requested in the same issue already ships as sortable({ drop: 'container' }); it now has a test for the announced target container on keyboard and touch.

  • ca5cc0e: Display text role (#350). New Heading component (level, size="display" | "display-2" | "display-3" | "h1".."h6") bound to --ca-font-display (the body font until fonts.display is set), the new --ca-display-tracking / --ca-display-leading tokens and the fluid --ca-font-size-display-* sizes. AuthCard, PageShell, EmptyState and SiteHeader take a display prop. defineTheme({ typography: { displayTracking, displayLeading } }) sets the tokens.

  • e5c7c0f: DocsHeader is the top bar of DocsShell as 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: Drawer gets chromeless and fullBleedBelow (#362), mirroring Dialog's chromeless. chromeless renders 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 by title (screen readers only), aria-label or aria-labelledby. fullBleedBelow="sm" | "md" | "lg" makes a left/right panel exactly the viewport width and --ca-vh height below that breakpoint (no radius, border or page strip, safe-area padding kept). .ca-drawer__body is now a flex column with min-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 the easing export). New Highlight and StrikeThrough components with animated, drawn in their final state under prefers-reduced-motion. Surface takes grain (true | "animated"), a paper-noise overlay tuned by --ca-grain-opacity / --ca-grain-size. Fix: --ca-font-mono (and fontFamily.mono) now starts with the system monospace instead of JetBrains Mono, which the kit does not load; set fonts.mono to pick another.

  • 446c722: HoverCard (#454): a rich preview that opens on pointer hover (after openDelay) 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 (use Popover for a tap-to-open preview). triggerChild/trigger, bind:open, onOpenChange, side, align, sideOffset. FloatingLayer forwards onfocusout.

  • c8e9492: ca-ui-mcp searches the gallery index. search_ui takes words plus facet filters (kind, subtype, style, motion, interaction, layout, colorMode, density, tech, cost, component), list_facets lists every value with its count for the current selection, and get_related returns the entries related to a demo. They read the same published facets as api/v1/index.json: a facet still under review is never searchable.

  • a5bc3b7: init --write now produces a SvelteKit app that passes vite build, svelte-check --fail-on-warnings and codeatlas-ui lint as written, on Kit 2 and Kit 3.

    • PWA and SEO are opt-in. The manifest route, service worker, offline page, robots.txt and sitemap.xml are written only with init --with pwa (also combinable: --with lint,e2e,pwa). A plain init writes no manifest link in <Seo> (the old default failed the prerender with a 404 as soon as the manifest route was removed). --with pwa can be added later to an app init already set up.
    • Lint-clean output. The copy init writes (layout title, offline page, manifest name) lives in src/lib/messages.ts (export const m), so codeatlas-ui lint no longer reports i18n-literal on a freshly initialised app.
    • Kit 3 #lib/* map. init adds "imports": { "#lib/*": "./src/lib/*" } to package.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-styled display: contents wrapper) and tsconfig.json instead of skip ... (not found). Kit 2's tsconfig.json gets allowImportingTsExtensions, noEmit and skipLibCheck, 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 the pages option) goes into the tsconfig exclude, so svelte-check no longer scans it, and src/routes/+layout.ts gets export const prerender = true;. init notes when the adapter major does not pair with the Kit major (@sveltejs/adapter-static@4 for Kit 3, @3 for Kit 2).
    • prerender.handleUnseenRoutes: "ignore" is written into the vite.config.ts that --with pwa creates on Kit 3 (no page links to /robots.txt or /sitemap.xml), and noted otherwise.
    • Fixed the public-origin note. Kit 2 rejects kit.paths.origin; the mechanism is kit.prerender.origin there and sveltekit({ paths: { origin } }) on Kit 3.
    • A re-run no longer mistakes the theme import init wrote into vite.config.ts for an app-owned theme.
  • a73cb68: inline on Dialog, Sheet, Drawer, Popover, DropdownMenu and Toaster renders the overlay in place instead of the top layer: position: absolute inside its container, never moved to <body>, no showModal() / 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 a data-preset or data-theme-scope island. openLayer takes the same inline option. The Theme Studio overlays screen now shows the real components instead of static replicas.

  • d101e43: TextField addons, the InputGroup (#447, part of #432): prefix and suffix put text or a Button/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, disabled and size="sm" reach the addons; --ca-tf-addon-bg sets the fill. .ca-tf__addon classes documented in docs/css-api.md.

  • 1263da3: New Kanban board (#361) on top of useDnd: 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-column onloadmore with 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 gains sortable({ drop: "container" }) (column highlight, no insertion line, to.index always 0) and dnd.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: Kanban takes a summary snippet 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. header still replaces the whole title row. The kanban docs show a per-column money total and a board-height parent without :global.

  • 62a8d05: Kanban takes headingLevel (2–6, like EmptyState and FormSection) for its column titles (#501). The default level changes from <h3> to <h2> (inside a PageShell, the page's section level), so a board under the page <h1> no longer skips a level and passes axe heading-order. A board under an <h2> of its own passes headingLevel={3} to keep the old markup. Only the tag changes, never the look; a custom header snippet and the mobile tab layout (no column headings) are unaffected.

  • f6085f9: KpiCard takes a chart snippet (a Sparkline under the number, next to the icon) and a hint line, so an icon + sparkline tile needs no Card + Stat + Sparkline composition. The chart is a snippet rather than a data prop to keep the /charts subpath and echarts out of the core entry. New part: .ca-kpi__chart.

  • 25bd205: New layers store: layers.modalOpen is 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, HoverCard and NavigationMenuItem no 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 a lang attribute per item; onselect(locale) may return a promise (the switcher is busy until it settles, value changes only on success, onerror on failure); the trigger shows the native name from 600px and a short code below (or always with compact). Compose it in the trailing slot of SiteHeader, now documented as the end of the bar. New i18n group localeSwitcher (en, pt-BR) (#349).

  • c172f2c: New optional entry @codeatlasdev/ui/map for map products. MapStage wraps maplibre-gl (default) or mapbox-gl and keeps the basemap in step with the theme (styles: { light, dark }, swapped with setStyle on the same map), draws deck.gl layers interleaved through MapboxOverlay, fits bounds with padding, and turns flyTo into jumpTo under prefers-reduced-motion. MapControls adds glass zoom, compass (and optional geolocate) buttons with 44px touch targets. tokenColor(token, alpha?), mapColors(), useMapTheme() and rgbaCss() resolve oklch() / 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 inside onMount, so the stage renders in SSR. Pin @deck.gl/* to ~9.4.

  • 8607da1: Menubar and MenubarMenu (#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 the DropdownMenu items unchanged. DropdownMenu gains onmenukeydown, ontriggerkeydown and openFocus.

  • abb6269: Migration tooling per major (#382). migrations/<major>.json is the machine-readable record of every breaking change from 1.0.0 to 4.0.0 (before, after, why, and whether migrate covers it: yes, partial, no); it generates MIGRATION.md, dist/migrations.json and a new MCP tool get_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 (Grid cols template strings, .ca-overlay selectors, setLocale, a bits-ui import, ...) 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 codemod selectable-to-ca-selectable (4.0.0: class="selectable", class:selectable, .selectable in a style block). The dev warnings of GlassCard, GlassSurface, Row and setLocale on the server cite their migration id. A major changeset must now carry a Migration: <id> line naming an existing entry, and a test fails otherwise.

    NavModule is basePath/items in every release from 1.0.0 on: the path/sections shape 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() or oklch(), with or without alpha, one rule per theme) into a typed theme.ts with 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 (--strict exits 1); doctor checks peer versions, optional peers, theme wiring, viewport meta, a root font-size, TODO(lib#n) markers and .ca-* selectors, one line per finding with a fix hint, and --fix renames the markers workarounds reads; lint is a UI guard that reads .svelte, .astro and .css (no hex, raw px, inline style, :global(.ca-*) outside ui-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,e2e writes the guard scripts and a Playwright config with a 390/768/1280 matrix and an axe spec. migrate renames the pre-1.0 NavModule keys path/sections to basePath/items (registry entry v1-navmodule-basepath-items). @codeatlasdev/ui/testing gains withTheme({ scheme, preset, style }), coarsePointer() and measure(el, props).

  • 330d761: NavigationMenu, NavigationMenuItem and NavigationMenuLink (#457): disclosure navigation for marketing headers, not role="menu". A nav with a list of links and aria-expanded/aria-controls buttons 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 after openDelay, switches at once while one is open and closes after closeDelay; a click toggles (touch). current sets aria-current="page"; description adds a second line to a panel link.

  • 98a7396: PageFrame: page container for map-first apps. mode map | split | page, side, width sm | md | lg | xl, a non-modal bottom sheet below 900px with peek | half | full detents (drag or keyboard), padded, headerBorder and fixed header / toolbar / footer slots. New stageInsets store (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" sets data-ca-stage="hidden" on <html> (#344).

  • cbc214c: PageShell: new aside snippet (companion column, sticky beside the content from a 60rem-wide shell, after it below) with asideLabel, and width="reading" | "wide" to cap the content column at 46rem. AppShellLayout: new collapseShortPanel prop 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, and caThemePlugin/caUI warn when a hand-set text, textSecondary or textHint is under 4.5:1 after compositing its alpha. PresetDefinition.tokens ({ status: { new: { light, dark } } }) emits --ca-token-status-new plus derived -subtle and -contrast per preset and scheme, with names typed through the CaTokenRegistry augmentation. tokenRgba("status.new") and onTokensChange(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's tokenColor now shares that resolver. #rrggbb presets behave as before.

  • 5eebf5e: Theme presets can own the primary-button and field roles: colors.<scheme>.brandFill, brandFillHover and brandContrast (--ca-brand-fill, --ca-brand-fill-hover, --ca-brand-contrast) and sizes.inputFontSize (--ca-input-font-size, still max(16px, …) on touch and below 900px). They are emitted with the same selectors as the other preset roles, so Button variant="filled" inside [data-preset="x"] needs no app CSS. caThemePlugin warns when brandContrast on brandFill is under 4.5:1 (caUI for 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 own ThemeIsland (scheme, preset); src shows a same-origin page in an isolated iframe.

  • c99fb14: Progress gets size="sm" | "md" | "lg" (4px, 6px, 8px; the default stays 8px) and honours a --ca-progress-height custom property for any other height (#358). Determinate and indeterminate bars both follow it and the ends stay pill-shaped.

  • 2ff23ff: AppShellLayout navigation for apps with many modules (#346): railLabels captions under rail icons, a railTop slot for a search / ⌘K trigger, NavModule.badge (number or 'dot') with badgeLabel folded into the button's accessible name, a bottom navigation that keeps bottomNavPrimary tabs and moves the rest into a "More" dialog by default (native <dialog>, bottomNavOverflow slot; opt-in moreSheet={NavMoreSheet} swaps in a full Sheet), and filterModules for role-based navigation (the active module is resolved on the filtered list). Rail takes top and labels, BottomNav takes primary and overflow.

  • 043c32e: Migrating a React app is now data, not prose (#383). equivalents/react-ui.json maps every shadcn/ui component (66) to the Radix primitive, Base UI package and MUI component that mean the same thing, the @codeatlasdev/ui target 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 tool get_equivalent("Dialog"), shipped as dist/equivalents.json, and generated into the migration guide, so the page cannot drift from the data. codeatlas-ui migrate-react lists every shadcn, Radix, Base UI and MUI import of a .tsx app with its replacement, and in an already-ported .svelte file renames className, onClick, htmlFor and the shadcn Button variants, 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*]. :disabled and [aria-disabled=true] get not-allowed. A custom clickable tile no longer shows the arrow cursor; an app overrides the rule with an unlayered cursor declaration, no !important. The stylesheet drops the now redundant cursor: pointer of .glass-btn* and .ca-rail-btn, the unused .ca-bar__actions rule and the unused --ca-grid-columns token to stay inside its 17250 B budget.

  • 5931a06: ResizablePanels, ResizablePanel and ResizableHandle (#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's minSize / maxSize. Arrow keys move 1%, Shift 10%, Home and End go to the limits (Up and Down in a direction="vertical" group, mirrored in RTL). persist keeps the layout in localStorage and ignores a stored layout that does not match the panels; onlayout reports it. Replaces react-resizable-panels / shadcn Resizable. New i18n group resizablePanels (en, pt-BR).

  • 194723a: AppShellLayout stage snippet (#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, and stageVisible={false} or data-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: SimpleTable gets 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) and headerCase="sentence". New TableRow (selected, onactivate: Tab, Enter/Space and click, nested controls never bubble), TableGroupRow (collapsible, aria-expanded, sticky label) and TableSortHeader (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 shared AudioContext is 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 in localStorage key ca-sound and 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 by designStyle()), tunable with --ca-sound-gain, --ca-sound-pitch and --ca-sound-brightness. Hover and slider tick cues are off under prefers-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 a sound prop (boolean | CueName) that emits through soundBus, a no-op hook exported from the root, so core stays silent without the entry. use:sound (the sound export 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 styling section per component (components.json): stable ca-* parts, internal parts (ca-x-* classes and @keyframes names, which can change in any release), component-scoped --ca-* variables and the data-* attributes its CSS selects on. It is surfaced in API.md, in dist/parts.json and in a new MCP tool list_parts; get_component returns it too. codeatlas-ui workarounds now also scans .css, .svelte and .astro for .ca-* selectors that are not a stable part, and --strict exits 1 on them (a ca-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__sep rule.

  • 3b50a5c: New Stat (#359): a card-less statistic with label, value, unit, hint, trend, size (sm | md | lg), align and skeleton. 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. KpiCard is now a Stat inside a Card, so both share one typography: the doubled inner padding (card padding + .ca-kpi__inner padding) is gone, and the unused .ca-kpi__top and .ca-kpi__value-row wrapper classes were removed; .ca-kpi__title, __value, __unit, __trend(--up/--down) and __icon stay.

  • b34ffd4: Stat and KpiCard take trendTone ('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 dropped trend and wrote the delta in hint can pass it again.

  • 7bc89c7: TextField: revealable adds a show/hide eye button to type="password" (aria-pressed, localized name, 44px hit area on touch, no layout shift), below renders a snippet under the field and its hint or error (a "Forgot password?" link, a strength meter) and requiredIndicator={false} hides the asterisk of a required field. New i18n group textField (en, pt-BR) (#348).

  • 3aacf62: ThemeIsland: a block or a whole page with its own preset and scheme. document also mirrors data-preset, data-theme-scope and the dark class onto <html> while mounted (so html/body, overscroll and color-scheme match) and restores the app's values on unmount, with overlapping islands and theme-store writes handled. SSR parity: setRouteTheme(event, { preset, scheme }) in a load, or caThemeHandle(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 and data-theme-scope resolve (#352).

  • c39a426: New ColorField (swatch, native picker, #hex/rgb()/oklch() entry and a live WCAG contrast readout) and SliderField (label, slider and an editable number with a unit). Slider takes a label so a thumb is announced as "Radius" instead of "Value 40". The color helpers behind them are public: parseColorInput, contrast, wcagLevel, toHex, toOklch, formatOklch, extractPalette and pickBrand (derive a brand from the pixels of a logo), plus presetContrastIssues and presetTokens for theme editors.

  • 37cda52: toast: optional description (secondary line) and a single inline action: { label, onClick } on toast.show and the success/error/warning/info shortcuts, which now also take the options object as the second argument (positional duration still 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 now role="alert", the others stay role="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, caption and num are size / leading / tracking / weight bundles (--ca-text-<role>-size|leading|tracking|weight, and --ca-text-<role> as a font shorthand), applied with <Text role> or .ca-text-<role>; num renders 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, -caption and --ca-label-tracking are 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.txt and the decision tables now teach the 4.6 APIs: a map-first recipe (AppShellLayout stage, MapStage, PageFrame), rail labels and the "More" sheet, ChipGroup, Stat, dense tables, Kanban, Drawer chromeless, toast actions, tone="neutral", ThemeIsland and per-preset button colors. The "common workarounds" page lists what each one replaces.

  • caf1fa7: AppList is generic: T is inferred from data, so the list of its filters, children and empty snippets is a UseListStateReturn<T> (rows are typed, searchKeys and idKey are checked against T) instead of any, and a snippet annotated with UseListStateReturn<User> type-checks. The recipes now keep app copy in a typed src/lib/messages.ts, so they pass codeatlas-ui lint; the new App copy page explains the i18n-literal rule and that pattern.

  • 7667a22: AppShellLayout: only the most specific nav item is active. With items at /items and /items/a, visiting /items/a marks just Item 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. findActiveItemPath follows it and the new findActiveItem(entries, pathname) returns the item itself.

  • 2ccbb9f: caUI (Astro) now puts the theme first in <head>, ahead of every stylesheet. It used injectScript("head-inline"), which Astro renders AFTER every bundled <style>: with inlineStylesheets: "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. caThemeHandle and caThemePlugin already emitted at the start of <head>.

  • 6d65542: AuthLayout: a brand snippet now replaces the whole logo. The default "CodeAtlas UI" name no longer renders beside it, in the side panel or the mobile strip; pass logoName to show a name next to your brand (#340).

  • 06fb8aa: AuthLayout: on a flat panel (panelTone="flat", or auto under the flat and outlined surface styles) --panel-text-secondary and --panel-text-tertiary are the theme's --ca-text-secondary and --ca-text-hint instead 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-contrast is 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-text is 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.ts measures every preset, light brands and colors.<scheme>.brand.

  • 35b5c9b: <Button type="submit"> and <Button type="submit" form="id"> type-check in apps without strict (the SvelteKit 2 scaffold): svelte-check widens a static type="submit" to string, which matched neither branch of the Button props union (form was never). The button branch now accepts the widened string. Documented on Button and Dialog: a submit button in a Dialog actions footer names its form with form.

  • ad005c0: Card and GlassCard: 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. New Card prop gap ('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 (axe scrollable-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. Menubar items get the 44px hit area under a coarse pointer. TextField can 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 getComputedStyle on <html>, so a chart inside a ThemeIsland or any data-preset / data-theme-scope element painted the page's colors, and its series palette ignored the island's preset. ThemeIsland now provides its element as the scope, and the new ChartScope (from @codeatlasdev/ui/charts) does the same for your own scoped containers. Without a scope nothing changes.

  • 73f38cd: CodeSnippet highlights 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 use scrollFocusable, so a long line no longer leaves a scroll region that only a mouse can reach (axe scrollable-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.css shrinks 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-style on <html> now renders as data-style="solid", the package default: no backdrop blur, opaque surfaces, no glow, the pulsing skeleton. Before, an app without the theme plugin (no themeScript, no caUI) kept the glass tokens, so every data-ca-blur element (chips, tabs, toasts, rail, panels) blurred. Glass, frosted and the other styles are unchanged: set data-style to opt in.

  • ce8ed51: Docs, skill, AGENTS.md, README, MCP list_surfaces and component JSDoc present the library as theme-driven with style as one knob (default solid) 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 pin material="glass". The liquid-glass npm keyword is replaced by theming and design-tokens. No API changes.

  • 866912f: FileUpload: the drop zone's accessible name now includes the visible hint (aria-labelledby over the label and the hint), fixing the axe label-content-name-mismatch / WCAG 2.5.3 failure (#490). With a custom children snippet the zone keeps aria-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: the get_demo call, 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 --write on SvelteKit 2 with @sveltejs/adapter-static creates svelte.config.js with the adapter and vitePreprocess, or adds adapter: adapter() and its import to an existing config that names no adapter. Before, vite build ran with no adapter and no build/ output. A config that already sets adapter is untouched; Kit 3 is unchanged (the adapter goes into vite.config).

  • 7fe318d: init on SvelteKit adds "prepare": "svelte-kit sync" to package.json when the app has no prepare script, so svelte-check on a fresh clone no longer stops with File '$app/tsconfig' not found. src/lib/messages.ts carries only appTitle unless --with pwa is given: the offline keys are written (or added to the file init wrote) only with the offline page that reads them.

  • b8e217e: Surfaces and chips inside a ThemeIsland (and a PreviewStage with a scheme or preset) 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 (a FileUpload zone, an AppForm footer) under dark text. The success, error, warning and info chip text follows [data-theme-scope] as well as .dark.

  • fe9c5d3: Kanban no 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-in and /sign-up (the Auth pages recipe), so copying it whole prerenders with adapter-static; before, /pricing, /docs and /contact failed 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/signIn constants at the top of the page that are in-page anchors (#pricing), instead of linking to /sign-up and /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, Drawer and Sheet (.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, same id).

  • 503a842: init --write adds the "imports": { "#lib/*": "./src/lib/*" } map to package.json and writes #lib/theme.ts / #lib/messages.ts imports 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/...ts on both majors instead of telling Kit 2 users to rewrite every import to $lib. An existing $lib import on Kit 2 keeps working and is never rewritten.

  • 9e751a2: llms.txt and the AGENTS.md block written by init say that dist/components.json keeps the components under .components[] and every other export under .exports[].

  • 3b76318: llms-full.txt keeps 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.txt links the agent-readable .txt form 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 (and get_component) now list the layout token props of Flex, Stack, Grid, Box, Container, Section and Surface (gap, align, justify, direction, wrap, columns, p, px, py) from their LayoutProps pick.

  • b8edc32: LoadingBoundary holds the placeholder for the full minDuration: 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 of performance.now().

  • de1c873: components.json (exports[].members), API.md and the MCP get_export now 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.md and the MCP get_component spell out the values of a prop typed with a literal-union alias (Avatar shape: 'circle' | 'rounded', size, status, Card headingLevel: 2 | 3 | 4 | 5 | 6, Button variant and about 100 more) instead of the bare alias name, and API.md keeps the description of each snippet (SkipLink children replaces the label).

  • 48e1d42: @codeatlasdev/ui/map: MapStage works with maplibre-gl 6, so the peer range is ^5.0.0 || ^6.0.0 again and you can take the fix for the critical attribution-sanitizer XSS in maplibre-gl 6.4.0 and earlier (GHSA-jrc7-96c5-q579, fixed in 6.4.1). With engine="maplibre" the deck.gl layers now go through MapLibreOverlay from the new optional peer @deck.gl/maplibre (~9.4.0, which supports maplibre-gl 4.5.1, 5 and 6) instead of MapboxOverlay, which throws Cannot read properties of undefined (reading 'height') on every frame there. engine="mapbox" keeps @deck.gl/mapbox. Install @deck.gl/maplibre instead of @deck.gl/mapbox when you use maplibre-gl. On maplibre-gl 6 the app must also call setWorkerUrl once (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 wide PageFrame no longer throws Invalid LngLat object: (-Infinity, NaN). The stage set the map's padding and passed the same padding to fitBounds, 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] set cursor: default in ca.base, which outranks the pointer cursor of ca.reset, so an app shell never showed the hand on clickable controls (nor not-allowed on 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 localStorage throws on access (private mode, blocked cookies, a sandboxed iframe) it used to abort before setting data-preset, data-style and the dark class, 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 failing removeItem while 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.setTokens now 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, plain Notifications at zero), so it contains the visible badge text (WCAG 2.5.3, axe label-content-name-mismatch). Custom notificationBell.label overrides 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 with side="left". The desktop data-side position rules out-ranked the mobile inset: 0 reset, so the panel kept left: 12px with width: 100% (right edge 402px on 390px). Fixes #489.

  • 57ff10e: PageShell: below 900px only Buttons in actions stretch across the row. StatusChip, Chip, Badge and plain text keep their natural width instead of becoming a full-width bar (#339).

  • e5cb35d: PageShell and PageFrame warn once in development when the page has no <main> landmark (no main/[role=main] ancestor and none in the document), pointing at the Standalone page recipe. Silent inside AppShellLayout, AuthLayout, DocsShell or your own <main>. llms.txt lists Standalone page first under "Start here".

  • 99ab5db: .ca-prose and .ca-code fences: the code inside a pre inherits the pinned --ca-font-mono stack instead of the browser's bare monospace, which Chromium could resolve to a different font (and line height) between two paints.

  • 0168dcf: Theme: --ca-font-size-body1 no longer sets the root font-size. :root keeps 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 on body. A denser body no longer shrinks labels, inputs, buttons and captions (#351).

  • b8e217e: scrollFocusable re-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 a PreviewStage frame left its scrollable region without a tab stop.

  • b8e217e: PreviewStage frames and ResizablePanels 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: SegmentedControl items 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 Switch inside a SettingItem to carry its own aria-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: a NavigationMenu placed directly in trailing is hidden below 600px, and trailing can shrink (min-width: 0). A marketing bar with three nav links, a LocaleSwitcher and 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 ×10 badge 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; positional duration still works. ToastItem gains count and ToastOptions is 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: VirtualList no 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 next onloadmore).

  • 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.

Install

bun add @codeatlasdev/ui@4.6.0

Special thanks

To our main supporters and special contributors, Fábio Henrique Araújo (@FabioHAraujo) and Richardson Douglas (@Richards0nd). 💛