Skip to content

Releases: codeatlasdev/ui

@codeatlasdev/ui@4.6.0

Choose a tag to compare

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

Read more

@codeatlasdev/ui@4.5.0

Choose a tag to compare

Minor Changes

  • c23cff4: i18n: the message catalog is split per group and each component bundles only its own strings. root: Button only drops from 27.89 to 23.91 kB gzip and AppShellLayout from 39.88 to 36.07 kB. Messages, getMessages, useT, I18nProvider, setLocale and registerLocale behave as before; useT() and getMessages() still return the full catalog, which is bundled only when you import them.

Install

bun add @codeatlasdev/ui@4.5.0

Special thanks

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

@codeatlasdev/ui@4.4.0

Choose a tag to compare

@github-actions github-actions released this 17 Oct 14:03
de861dd

Minor Changes

  • 1c6653e: NavItem in the AppShell panel gets color (a decorative dot), leading (a snippet that replaces the icon), count (a neutral total, announced as a bare number instead of "N unread", 1.2k above 999) and actions (a snippet of DropdownMenuItems behind a "…" button named "Actions for ") (#275). NavSection.onReorder and NavModule.onReorder make the rows reorderable with the useDnd primitive: a grip per row, mouse, touch press-and-hold and keyboard, with announcements; the shell reports { id, from, to } (new NavReorderEvent type) and never reorders items itself. PanelNavItem now 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-item is the row, not the anchor. New messages appShell.itemActions and appShell.moveItem.

  • 1c6653e: AppShellLayout takes panelWidth (px, held between 160 and 480, default 200) and resizablePanel (#292). The content offset and the open and close animation follow the width, so a panel for long labels no longer misaligns the page. resizablePanel adds a keyboard-operable drag handle on the panel's trailing edge (Arrow keys 8px, Shift 32px, Home and End for the bounds); with persistPanel the chosen width is stored in ca-shell and restored by the inline script before the first paint. New appShell.resizePanel message and .ca-panel__resize class.

  • ca80cfd: Combobox: multiple turns it into a searchable multi-select (value is string[], aria-multiselectable listbox, list stays open, removable chips, Backspace on an empty query removes the last chip) and creatable works in both modes. CommandPalette: search(query, signal) adds debounced (searchDebounce), abortable server-side results with loading, empty and failed states announced through announce(), plus onSelect and the exported CommandResult type.

  • 49116d1: DataTable gains row groups, column resize, row reorder and safer interactive cells (#283). groupBy renders one <tbody> per key with a collapsible header (aria-expanded, groupLabel to customize). meta.resizable on a column adds a window-splitter handle (mouse, touch, keyboard arrows, Shift, Home, End, Enter) driven by size, minSize and maxSize; the new DataTableColumnResize is exported for hand-built headers. onReorder adds a drag grip per row on top of the new useDnd primitive, moves rows between groups and reports { id, from, to } (DataTableReorderEvent); rowId gives rows stable ids and pageSize sets the page size in simple mode. A click on a link, button, input or other control inside a cell no longer fires onRowClick (add data-no-row-click to 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(), applyMove and <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 through announce() (English and Portuguese built in, useDnd({ messages }) for others), auto-scrolls near the edges of a scrolling container and drops its animations under prefers-reduced-motion. The engine measures the boxes once per drag and only moves a clone, a drop line and data-dnd-* attributes, so the list does not re-render until onDrop. <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 to core.css or to the i18n catalog.

  • 946d69d: Drawer can be named without its default title: pass aria-label, aria-labelledby (your own header) or hideTitle, and id, data-* and other attributes now reach the <dialog> (#285). AlertDialog gains confirmProps and cancelProps (attributes for the two buttons) and cancelVariant (#287), plus a children body, a description that accepts a snippet and confirmDisabled for type-to-confirm (#278).

  • e983caa: DropdownMenu is controllable and can open at a point. bind:open and onOpenChange read and set the open state; anchor={{ x, y }} opens the menu at a viewport point (a right-click menu, no trigger needed) 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: DropdownMenu gains selection items. DropdownMenuCheckboxItem (role="menuitemcheckbox", bind:checked, onCheckedChange) and DropdownMenuRadioGroup + DropdownMenuRadioItem (role="menuitemradio", bind:value, onValueChange) render aria-checked and a check indicator, stay open after a pick (closeOnSelect closes), and join arrow, Home, End and typeahead navigation (the menu now also recognizes menuitemradio).

  • e983caa: DropdownMenu gains submenus. DropdownMenuSub takes a trigger snippet holding a DropdownMenuSubTrigger item (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, TextArea and FileUpload spread every other attribute and handler (data-*, maxlength, inputmode, onclick, onkeydown, name, capture...) on the native <input>/<textarea>, and take a bindable ref with that element. Checkbox onchange now receives the native event as a second argument, and its forwarded onclick makes Shift range selection possible. TextField/TextArea gain onfocus and hideLabel (label kept for screen readers with .ca-sr-only; aria-label also works without a label).
    • Select accepts aria-label and aria-labelledby for 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.
    • FileUpload gains ref, an open() instance method for a button outside the dropzone, and list={false}: a plain picker whose onchange receives only the files just picked, with no built-in list and nothing kept in files (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, Tooltip and Toast appear and leave in place, while hover and focus color transitions keep --ca-duration-*. New derived tokens --ca-enter-fast, --ca-enter-base and --ca-enter-slow; MotionStage demos render their final state when the theme turns entrances off, as under prefers-reduced-motion (#268).

  • 16f89dd: Popover is controllable. bind:open and onOpenChange read and set the open state, and the children snippet receives close, which closes the popover and returns focus to the trigger, so a choice made inside it (a Calendar day, an option) can dismiss it. Existing popovers are unchanged.

  • 8c24c31: New --ca-radius-round token (999px × --ca-radius-scale): "fully rounded at the current scale". SwatchGroup swatches, the AppShell active pips (.ca-nav-item__pip, .ca-rail__pip) and the new-item dot, and .ca-nav-item__badge use it, so a theme with radius: 0 squares them with no app CSS (#267). --ca-radius-chip now aliases it. --ca-radius-pill stays a fixed 999px for functional shapes (Switch, Slider, Progress, scrollbars) and for controlRadius: "pill".

  • 946d69d: Tabs gains variant="line" (bare text tabs with an underline on the active one, for page headers), and TabsList and TabsTrigger forward rest attributes: aria-label names the tablist, data-*, id and event handlers reach the trigger button (#286). A TabsTrigger now points aria-controls at its panel only while a TabsContent with the same value is mounted, so tabs used as view switchers no longer reference missing elements.

  • 2ab0d93: ToggleGroup gets required: pressing the active item no longer clears the selection (radio-like, onchange stays silent; type="multiple" keeps its last pressed item). An empty group still selects on the first press. Closes the gap with SegmentedControl for filters that must look like contiguous buttons.

Patch Changes

  • 05cfbfc: AppShellLayout: an item at path: "/" is active on / only, no longer on every page, and a module at basePath: "/" 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 (/settingsfoo no longer matches /settings).
  • 98d2ace: Astro: the theme now survives <ClientRouter /> (View Transitions) navigations. caUI registers an astro:before-swap handler that carries #ca-theme, #ca-runtime-tokens, the theme-color meta and the theme attributes onto the incoming document (#265). codeatlas-ui init on Astro also adds svelte() from @astrojs/svelte to integrations, and notes npx astro add svelte when the dependency is missing (#273).
  • 3ae1f41: Button with href now types the anchor's own attri...
Read more

@codeatlasdev/ui@4.3.0

Choose a tag to compare

Minor Changes

  • a1c0d04: Demo sidecars accept names (motion vocabulary) and motion (exact property, duration, easing, delay, stagger, spring). They are printed in e/NNN.txt and p/NNN.txt, carried in catalog.json and catalog.tsv, and ca-ui-mcp search_demos matches names and aliases before summary and tags.
  • a1c0d04: MotionStage and the MotionCtx type: 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 with aria-pressed, and under prefers-reduced-motion never autoplays and tells the demo to render its final state (ctx.reduced). ctx.cycle increments each loop so a demo can re-run its animation. Labels come from the i18n catalog (motionStage.pause, motionStage.replay) and can be overridden with pauseLabel / replayLabel.

Install

bun add @codeatlasdev/ui@4.3.0

Special thanks

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

@codeatlasdev/ui@4.2.0

Choose a tag to compare

Minor Changes

  • b65d56d: AI feedback loop. The ca-ui-mcp server has a new report_issue tool: 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 a gh issue create command. It never posts. bunx @codeatlasdev/ui workarounds lists 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 detached Image() probe, so no double download) and passes loading, referrerpolicy and crossorigin through, so loading="lazy" keeps a 200-row list from firing 200 requests and referrerpolicy="no-referrer" works with CDNs that reject a cross-origin Referer. fallback now also takes a snippet (an icon for groups, channels and bots), and onerror fires 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. The data-status attribute on .ca-avatar is gone.
  • 80c0336: Card has a padding prop ('none' | 'sm' | 'md' | 'lg', any CSS length, or a number of pixels) so a one-off density no longer needs an inner wrapper; flush stays 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 from peaks, slider semantics, playback rate), MediaGrid (image, video and document tiles opening a Lightbox gallery) and VirtualList (windowing plus load-older at the top that keeps the scroll position). New chat, audioPlayer, mediaGrid and virtualList messages in en and pt-BR.
  • 80c0336: CopyButton takes aria-label for 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 i18n copyButton.label. The label is ignored when label is 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.json sidecar (summary, use, spec, tags, shape), so get_demo, search_demos and /api/v1/en/{e,p}/NNN.txt return 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 bad shape, or a spec line that quotes a token that is not in the demo source. New codeatlas-ui pick [ids...] opens the gallery, listens on 127.0.0.1 with a random per-run token and prints the ids you send with "Send to AI" (#005 #012).
  • d99cead: codeatlas-ui init scaffolds SEO and PWA on SvelteKit (#205): prerendered manifest.webmanifest, robots.txt and sitemap.xml routes built with the seo helpers, a service worker (src/service-worker/index.ts on $app/service-worker, $app/manifest and $app/env for Kit 3; src/service-worker.ts on $service-worker for Kit 2) with a prerendered /offline page, and a default <Seo> with canonical and manifest link in the root layout. Existing files are never overwritten; after creating them init reminds the app to set paths.origin (otherwise Kit's prerender placeholder origin reaches the sitemap and canonical URLs), add the icons and exclude src/service-worker from tsconfig.json on Kit 3.
  • d99cead: New InstallPrompt (#208): a dismissible banner that offers to install the app. On Chromium it keeps beforeinstallprompt (no browser mini-infobar) and shows an Install button that opens the native dialog and reports the choice with onchoice; 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 for dismissDays (default 14) after the user closes it. Strings are in the installPrompt i18n group (en, pt-BR); the root: Button only size 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 as LightboxItem) plus a bindable index add previous/next buttons, ArrowLeft/ArrowRight and swipe (no wrap; the end buttons are aria-disabled so focus stays put), a counter, caption and sender/time line, video with controls (paused when you move on), and a Download button (ondownload or a plain <a download>). src/alt still work as a single-item shortcut. New lightbox.previous, next, download and counter labels in en and pt-BR; the close button moved into a .ca-lightbox__tools bar.
  • 3180023: Leading icon slot for DropdownMenuItem and TabsTrigger, and a documented pattern for brand logos (#194). icon is a snippet rendered in a fixed 14px box with the standard gap and aria-hidden (the label stays the accessible name), so icon size and alignment no longer drift between menus. brandIcon marks the icon as a fixed-color brand mark: no highlighted, disabled, active or destructive color reaches it (trademark guidelines forbid recoloring). New SchemeAsset takes light and dark snippets and shows the one for the kit's color scheme (.dark on <html> or a data-theme-scope island) 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: LoadingBoundary wraps <svelte:boundary pending> with a 200 ms delay and 500 ms minimum so a loading UI never flashes (default placeholder is Skeleton rows, aria-busy, named region). @codeatlasdev/ui/kit gains NavigationProgress (top bar driven by navigating from $app/state) and the pendingLink() attachment that sets data-pending on the link being navigated to. A test now proves AppShell stays mounted across 10 client-side navigations.
  • df50d86: New RecordList, RecordRow and RecordCell: 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, one columns template 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 an aria-hidden header whose labels become per-cell dt when stacked. It replaces the hand-rolled flush Card + grid + :global row CSS, and is much shorter on a phone than Table responsive="cards".
  • d99cead: <Seo> completes the social and language tags (#207): og.defaultImage is the site-wide card used when a page has no og.image (also feeds twitter:image and the summary_large_image card), twitter:title and twitter:description mirror the page title and description, and the new lang prop sets og:locale and keeps <html lang> in sync on the client.
  • 81910d1: skeleton prop on Avatar, KpiCard, TextField, Table, Card and GlassCard: 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 with aria-busy="true"; a skeleton TextField is disabled and an Avatar makes no image request. Reduced motion stops the sweep and forced colors draw an outline. New global [data-skeleton] rule and ca-shimmer keyframes in utilities.css.
  • f889334: Every backdrop blur now comes from one place. Components no longer declare backdrop-filter: they set data-ca-blur="<role>" (or are a .ca-surface) and styles/surface.css paints 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). FloatingLayer gains a blur prop (overlay or tooltip). lint:tokens rejects backdrop-filter outside surface.css and materials.css. The MCP server gains list_surfaces, generated from the [data-style] selectors in materials.css, and dist/surfaces.json ships with the package.
  • 3180023: TabsList handles its own overflow (#190). It keeps the width of its tabs inside Tabs (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 optional fade prop masks the edge that still hides tabs, like the AppSettings nav. The wrapper div with overflow-x: auto and 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 moda...
Read more

@codeatlasdev/ui@4.1.0

Choose a tag to compare

Minor Changes

  • ccae97c: Numbered demo catalog (ca-prompt): every docs demo has a stable #NNN id. The package ships demos.json (export @codeatlasdev/ui/demos.json) and dist/api/v1 reference texts, and ca-ui-mcp gains search_demos and get_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.

Install

bun add @codeatlasdev/ui@4.1.0

Special thanks

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

@codeatlasdev/ui@4.0.0

Choose a tag to compare

@github-actions github-actions released this 15 Oct 21:07
ad309a6

Major Changes

  • b99075a: AppShellLayout renders its content region as <main id={mainId} tabindex="-1"> by default, so a page built on it has a <main> landmark (axe landmark-one-main, region) and the skip link lands on it. It was a plain div. Pass mainId={null} when your page renders its own <main id="main" tabindex="-1">: the region is then a plain div, never a second landmark. If you wrapped your content in <main> inside the shell, remove it or pass mainId={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 .selectable while the selection CSS used .ca-selectable, so neither class restored both. The filter and its docs now say .ca-selectable, matching the ca-* class contract. Clean cutover: .selectable was never documented outside a JSDoc line, and no stylesheet ever targeted it, so apps using it must rename it to ca-selectable (right-click on a .selectable region is now blocked again until they do).

Minor Changes

  • 6e39edc: caBrandPlugin writes src/generated/brand.ts as 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. New codeatlas-ui brand command and brandUrls() / runBrand(): render the icons and the module without a Vite run, so a job that only runs svelte-check on a fresh clone type-checks (svelte-kit sync does not load Vite plugins).
  • c2d255d: FormSection accepts headingLevel (2 | 3 | 4 | 5 | 6) and AppSettings/AppForm provide the section heading level like PageShell, so FormSection and EmptyState titles inside them render as <h2> under the page <h1> (axe heading-order) instead of <h5>. Outside a page layout the default stays <h5>. Only the tag changes, never the look.
  • af02ce2: RadioGroup, Checkbox, Switch and Select accept name, so their value is part of FormData on a native form submit (progressive enhancement, SvelteKit form actions without a hidden-input workaround). Select renders a hidden input carrying the selected value when name is set.
  • e7b85e7: ogCard accepts lang (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, Stack and Grid are generic on as: <Stack as="form" novalidate onsubmit={...}> accepts the form's attributes, and the handler's currentTarget is an HTMLFormElement (same for as="a", as="label", as="button"...). An attribute that does not belong to the tag is a type error. Exports PolymorphicProps and ElementTag.

Patch Changes

  • 153be87: centsToDisplay keeps the no-break space Intl puts after the currency symbol ("R$\u00a0123,45"), so a price no longer wraps between the symbol and the number. MoneyInput still shows and parses a plain space in its editable text.
  • a517102: toCsv, toJson, ExportColumn and ExportMenu accept any object row type (T extends object), so rows typed with an interface no longer need a cast. key is still checked against keyof T.
  • 8d25c2f: caErrorPlugin({ scaffold: true }) writes a valid src/error.html: the theme init script now sits in <head> instead of inside <style>, the page uses the app theme (theme option, same object as caThemePlugin) instead of hard-coded amber, its copy and lang follow the new locale option (en default, warns when unset), and the javascript:history.back() link is gone (CSP-safe). The scaffolded +error.svelte uses tabs and double quotes like the other generated files. init now writes caErrorPlugin({ scaffold: true, theme }).
  • 9e5655f: init without --write is now a true dry run: it prints would create <file> instead of writing new files. It also stops creating src/lib/theme.ts (src/theme.ts on Astro) when vite.config.ts / hooks.server.ts already import theme from elsewhere, and wires the files it adds to that import.
  • a7f7e96: Docs and agent skill: apps must install @lucide/svelte themselves. Under Bun's isolated linker the package's own dependency is not importable from the app.
  • 3ef5b11: migrate keeps the default thickness of GlassCard and GlassSurface: a usage without thickness (default regular) becomes elevation={3} instead of dropping it, so the migrated card keeps its fill, blur, shadow and rim (Card and Surface default to elevation={1}).
  • 33ad505: migrate looks up typescript in the project directory first (then next to the CLI) and prints migrate needs typescript (bun add -d typescript) with exit code 1 when it is missing, instead of crashing with Cannot find package 'typescript'. This also fixes bunx @codeatlasdev/ui migrate in layouts where the CLI cache directory cannot see the project's typescript.
  • b7cdd79: migrate now ends with a list of the usages it left for you (file:line Component: reason (recipe)), and --strict exits with code 1 when any remain. A Grid or Stack whose cols/align could not be mapped still gets its gap converted: gap={20} next to cols="2fr 1fr" becomes gap="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/Drawer over another no longer loops on focusin (Maximum call stack size exceeded); closing it hands focus back to the one below.
  • daccdc8: .ca-prose heading anchors stay visible on touch screens: a stray second @media (hover: none) block hid them with display: none, contradicting the rule above it. New "Release lists" docs page documents the ca-release* markup and the version-id anchor convention.
  • c0bc624: --ca-scrim-blur now drives the modal scrim (#135). It was declared as none but never read: the ::backdrop of Dialog, AlertDialog, Sheet, Drawer, CommandPalette and DocsSearch hardcoded blur(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 plain blur(4px), now with the shared saturate(180%)).
  • 5a3ede3: Agent skill and AGENTS.md import ThemeOptions from @codeatlasdev/ui/vite (/astro in Astro), like the docs and init. The package root pulls .svelte files that Node cannot load from vite.config.
  • 8185835: New --ca-tooltip-blur token for the Tooltip surface blur (#137). It defaults to var(--ca-blur-ultra-thin), so the look is unchanged; --ca-tooltip-blur: none gives an opaque tooltip without re-tiering the shared ultra-thin blur (.glass-ultra-thin, .glass-btn-clear, --ca-upload-blur).
  • 3dda885: @codeatlasdev/ui/vite exports designStyle, DESIGN_STYLES, mergeTheme and DesignStyleId, so a theme.ts shared with vite.config can use a design recipe (including the 3.0 migration designStyle("liquid", { ... })) in plain Node. The theming guide and the init theme comment now point to this entry instead of /astro.

Install

bun add @codeatlasdev/ui@4.0.0

Special thanks

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

@codeatlasdev/ui@3.1.0

Choose a tag to compare

Minor Changes

  • 0579bb3: .ca-prose styles 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__meta row of date and links, then the rendered notes), add a .ca-release-jump pill index and close with .ca-release-thanks. Anchored releases clear a sticky DocsShell header (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 custom color). 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: generateBrand and 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: FilterGroup draws its label with --ca-text-secondary instead 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: KpiCard is built on Card instead of the deprecated GlassCard, so rendering it no longer logs the [ca-ui] deprecation warning in dev. The look is unchanged (elevation 3).

Install

bun add @codeatlasdev/ui@3.1.0

Special thanks

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

@codeatlasdev/ui@3.0.0

Choose a tag to compare

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-md is 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-rim to transparent and draw --ca-mat-edge as a hairline, so .glass-tinted, .glass-prominent, .glass-glow, .glass-active, .glass-btn-prominent and the AppShell nav pills lose their halo and rim without a component change. The page mesh (--ca-page-mesh) is drawn only by glass and frosted.
    • 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 }), or set defaultStyle: "glass", paper: "mesh", radius: 1, iconStroke: 2 and typography: { 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 (designTokens emits 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 (default true) now keeps the panel open/closed, the selected module and the section open map in localStorage["ca-shell"] (the old ca-panel-open key is migrated), and the panel scroll per module in sessionStorage. An inline script emitted right after the sidebar restores them before the first paint and sets data-ca-panel / data-ca-module on <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 over defaultOpen; 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. caBrandPlugin and generateBrand (@codeatlasdev/ui/vite) render a content-hashed favicon (SVG that follows prefers-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; resolveBrand and renderMark expose the resolved sRGB colors. iconLinks and ogCard (@codeatlasdev/ui/seo) emit the head links and the 1200x630 social-card HTML for renderOgImage. <Seo> now also writes og:image:type and twitter:image. @resvg/resvg-js and png-to-ico are optional peer dependencies.

  • 1eeca1c: Surface engine and layout primitives (#112).

    New components: Box, Flex, Stack, Grid, Container, Section (layout), Surface, Card and SurfacePanel.

    • Layout primitives take token props (gap, p/px/py, align, justify, direction, columns, wrap) and a polymorphic as. A prop renders as a data-* attribute (data-gap="4"), a responsive object ({ base: "2", md: "4" }) as data-md-gap="4", styled by styles/layout.css in @layer ca.utilities. No inline style.
    • Surface has orthogonal axes: elevation 0-5, material auto | solid | frosted | glass | liquid, tone, variant filled | outlined | ghost, radius, interactive, glow, as. One .ca-surface rule reads --ca-surface-* variables. material="auto" follows <html data-style>; the explicit materials look the same under every style. It never sets will-change or isolation, and it has forced-colors and reduced-transparency entries.
    • Card (title, headingLevel, href, flush, full, headerActions) is built on Surface; SurfacePanel is a raised <section> (the AppShell sidebar Panel keeps its name).
    • Docs: Surface, Card, Layout primitives; docs/css-api.md lists the new ca-* classes and the data attributes.

    Behavior changes to review in Stack, Grid and Row (they were the only layout components with inline style):

    • Stack and Grid gap is now a spacing step string (gap="4" is 16px), not pixels. align / justify use start | center | end | between | ... instead of raw CSS values. Grid cols is now columns (1-12, or a responsive object); rowGap, colGap and CSS template strings ("2fr 1fr", repeat(auto-fill, ...)) are gone: write grid-template-columns in a class.
    • Row is deprecated in favor of Flex. It still renders .ca-row and maps a pixel gap to the matching step.
    • GlassCard and GlassSurface are deprecated in favor of Card and Surface, and removed in the next major. They render the new components, keep their ca-glass-card* and glass* classes and look the same under every data-style, and log a [ca-ui] warning in development.

    Size: styles/core.css grows from 9.6 to 13.0 kB gzip (limit 9.6 to 13.6 kB), global.css from 12.6 to 15.9 kB: layout.css is ~2.2 kB (every token prop and step at the base and four breakpoints, generated by scripts/build-layout.ts) and surface.css ~1.2 kB (the five materials by six elevations). JS entries are unchanged.

    Migration: bunx @codeatlasdev/ui migrate --write src rewrites GlassCard to Card material="glass" (thickness to elevation, tinted to tone="brand", glowing to glow, static padding to p), GlassSurface to Surface (refraction to material="liquid"), Row to Flex, and Stack/Grid pixel gaps, cols and align/justify values. Dynamic or off-scale usages are left as they are, with their import, for you to finish.

  • 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 are popover elements. Both live in the top layer, so stacking no longer depends on z-index: a menu inside a dialog is always on top. The --ca-z-dropdown and --ca-z-tooltip tokens 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 a layerchange CustomEvent on document) fires on every layer open and close with the open layers and their rectangles, so hosts can park or snapshot native views.
    • The .ca-floating wrapper div is gone: .ca-floating is now on the layer element itself (the one carrying data-state/data-side), and .ca-date__float was removed.

Install

bun add @codeatlasdev/ui@3.0.0

Special thanks

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

@codeatlasdev/ui@2.3.2

Choose a tag to compare

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 on pagehide) 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.

Install

bun add @codeatlasdev/ui@2.3.2

Special thanks

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