Skip to content

0.2.0

Latest

Choose a tag to compare

@srivera145 srivera145 released this 21 Oct 17:56
· 2 commits to main since this release

This was going to be 0.1.4. One default below changes how existing pages look, and in 0.x a breaking change moves the minor version, so it is 0.2.0. A ^0.1 range or a CDN URL pinned to @0.1 stays on 0.1.3 until you move it to ^0.2 or @0.2.

Breaking

  • Primary buttons are lighter in dark mode. .btn-primary now fills with --brand and --brand-hover (see An exact brand colour reaches .btn-primary under Fixed). In dark mode those are the 400 and 300 steps, so the button takes the lighter colour a link uses, with black text. Until now it was the 600 step with white text in both schemes. Light mode is unchanged. To keep the old dark look, add one line to your CSS: @layer app.overrides { .btn-primary { --brand: var(--brand-600); --brand-hover: var(--brand-700); --btn-text: var(--text-on-brand); } }. It matches 0.1.3 at rest and on hover, in both schemes.
  • The npm package no longer carries the docs build data. dist/api.json, dist/api-buckets.json, dist/usage.json and dist/collisions.json describe Deck to its documentation site and editor extension, and nothing in a page or in npx @echodial/deck reads them. The tarball is 2.3 MB unpacked instead of 4.3 MB. If a script of yours read them from node_modules, take them from the Composer package or the repository, which still have them.
  • Two fixes below also change existing markup, and each says what to do if you relied on the old behaviour: .scroller's new padding (Focus rings inside lists and scrollers are whole and Scrolled cards rest at the gutter), and the on state a .btn now has with aria-pressed="true" (Toggle buttons, under Added).

Fixed

  • Modals open centred. .modal now sets margin: auto. The reset's * { margin: 0 } removed the browser's own centring of modal dialogs, so every .modal opened with showModal() sat in the top-left corner of the window.
  • Icons in running text stay on the line. .icon is now display: inline-block. The reset's svg { display: block } put an icon inside a sentence on a line of its own, despite .icon's baseline alignment. Icons in flex and grid rows (buttons, alerts, tiles, fields) are laid out exactly as before, because flex and grid items are blocks either way.
  • Long modals keep their footer. An open .modal is now a flex column, and so is a <form method="dialog"> wrapped around its regions. .modal caps its height at min(85dvh, 48rem) and clips, and .modal-body was meant to scroll, but an open modal was laid out as a block, so the body never shrank: in a window shorter than the dialog, everything past the cap was clipped, the footer and its buttons included. Now the body scrolls while the header and footer keep their height. flex-direction: column is on .modal itself rather than on .modal[open], so the regions stay stacked while a closing modal fades out.
  • Dark mode no longer has light blocks in it. .progress, .ring, .spinner, .skeleton, .range and .range-pair drew their tracks with --ink-100 and --ink-200, and the status badges, alerts and .banner-warn filled with --good-100, --warn-100 and --bad-100. All of these are fixed light values, so each drew a near-white shape on a dark page, and a .btn-ghost inside .alert-good, .alert-warn or .alert-bad was all but invisible (1.03:1). They now use theme-aware tokens. Light mode is unchanged, pixel for pixel.
  • The combobox placeholder is escaped. data-placeholder was written into the combobox's markup unescaped, so a placeholder containing a double quote could add attributes to the input, an onfocus handler included, and an ordinary placeholder with a quote in it was cut short. It is now escaped like every other string the combobox renders. The markup and data attributes are unchanged.
  • .banner-close clears the 44px touch target. It was only as big as its icon, 25×22px. It is now a --tap square with the icon at its old size, and it reaches into the banner's padding rather than making the strip taller. A .btn.banner-close is unchanged, because .btn already had a 44px target.
  • The combobox list opens next to its field. Where the browser has anchor positioning, the list covered the field it belonged to on every page, because position-area was measured inside .combo, which is only as tall as the control. On a phone inside a .card, the list was laid out against the card rather than the screen, since the card's contain: layout captures even position: fixed, and the card's overflow: clip then cut it off. deck.js now opens the list as a popover in the top layer, so no ancestor can clip it, and places it with anchor positioning, the Floating UI adapter, or its own placement, in that order. On a phone it stays anchored to the field, with 44px rows, instead of becoming a sheet across the bottom of the screen.
  • Remote comboboxes keep earlier choices, and post them. With data-url and data-multi, a choice made under one query was dropped by the next unless the new results happened to contain it, and no remote choice ever reached the <select>, so the form posted none of them. The selection is now kept apart from the results, every chosen value is written into the <select>, a token can be removed whatever the current results are, and a slow reply to an old query no longer replaces a newer one's results.
  • Long comboboxes open at once. Only the options near the scroll position are drawn, so 10,000 options open in about 16ms instead of 1.4s (Edge). The arrow keys still reach every option and wrap at the ends, PageUp and PageDown move a screenful, and the active option is announced through aria-activedescendant with its place in the whole list. The docs had always said aria-activedescendant was set; it was not. ArrowUp with nothing active now goes to the last option rather than the second-to-last.
  • Focus rings inside lists and scrollers are whole. .list clips, so a focused .list-row lost the sides of its ring, plus the top on the first row and the bottom on the last. The ring is now drawn inside the row, and follows the list's rounded corners. .scroller clipped the top of every focused item's ring and faded the ring of an item at the end edge. It now has 4px of top padding, so content below a scroller moves down 4px. Its fade lifts while keyboard focus is inside.
  • Scrolled cards rest at the gutter. .scroller has scroll-padding-inline equal to its own inline padding. Before, the first card snapped flush against the screen edge on load, and so did any card scrolled into view. A scroller you have made flush with padding-inline: 0 needs scroll-padding-inline: 0 as well.
  • .card-link responds to keyboard focus. Tabbing to the stretched link, or to anything else in the card, gives the card the same lift as hover.
  • .nav-link keeps one height with or without an icon. It is now inline-flex with centred items, so an icon no longer sits on the text baseline, where its size and the font could make the link taller. Text-only links are the same size as before.
  • A .btn label split at a bare <span> reads as one phrase. Save <span>changes</span> used to put the 8px gap between the words. A span with no class and no child elements now joins the label's text. A span with a class, such as a badge or .sr-only, keeps its box and the gap.
  • .form-actions-sticky sits above .tabbar. It used to slide underneath the tab bar. body:has(.tabbar) now publishes its clearance as --tabbar-clearance, the sticky bar uses it, and the bar drops its own safe-area padding while the tab bar is showing.
  • aria-pressed and aria-selected both mean "on". .chip, .reaction and .datepicker-preset used to respond only to aria-pressed="true", and .segmented only to aria-selected="true", so the wrong one showed no state and raised no error. All four now accept both, with the same look as before. deck.js moves either attribute when a .segmented is clicked, and no longer selects an aria-selected table row that contains one.
  • Small pointing icons mirror under dir="rtl". The automatic mirror in src/19-logical.css matched #chevron-right but not #chevron-right-sm, so an .icon-sm arrow or chevron kept pointing the wrong way in a right-to-left page. Every pointing icon is now listed with its -sm cut.
  • .g-scrim darkens an <img> child. The scrim painted under every child, so with the photo as an <img>, rather than as the element's background, the caption sat on the bare image. An img, picture, video, canvas, svg, iframe, .lazy or .video child now sits under the scrim; other children still sit above it.
  • A failed .lazy image shows that it failed. deck-extras.js set .is-error on the frame and nothing styled it, so the shimmer ran forever. The shimmer now stops and a broken-image glyph appears in the frame. The script also catches an image whose markup src failed before the script loaded, which fired no event.
  • .grid-fixed-2, -3 and -4 work without .grid. They set only the columns, so on their own they did nothing. Each now sets display: grid and the gap itself; grid grid-fixed-3 is unchanged, and print unwinds them like .grid.
  • .split-rail-start is described correctly. It sizes the first track as the rail, so the rail goes first in the markup. Its comment and the split docs said the opposite, and the docs example showed the main column squeezed into the rail's width.
  • .file takes a dropped file. It looked like a drop zone and ignored drops. With deck-extras.js, dragging a file over it sets .is-over (a solid border and the focus ring), and a dropped file goes into the input as a pick would: filtered by accept, one file unless multiple, then input and change. A drop the page's own handler has already taken is left alone.
  • SortableJS can load after Deck. With Deck deferred, a SortableJS deferred after it or loaded async was not there when the adapters first ran, and the list was left on the native fallback for good, so drag handles did not work on first load. A list on the fallback is now handed to SortableJS when it arrives (at DOMContentLoaded or load, or on any later Deck.init()). The native fallback now honours data-handle.
  • Small icons from Deck's helpers use the small cut. Deck::icon($name, 'icon icon-sm') and the icons deck.js renders with .icon-sm (date picker, combobox, toasts) pointed at the regular #name symbol rather than the heavier #name-sm drawn for 16px.
  • .video-poster shows its play button. The poster image and the button were two grid rows, so the image overflowed the frame and the button sat below it, clipped. The image now fills the frame and the button is centred on it.
  • An exact brand colour reaches .btn-primary. The documented override, --brand and --brand-hover on :root, recoloured links and outline buttons but not primary buttons, which filled with the --brand-600 and --brand-700 ramp steps. They now fill with --brand and --brand-hover. Light mode looks exactly as before. In dark mode a primary button is now the lighter step a link uses, with black text worked out from the fill, where it used to be the 600 step with white text in both schemes.
  • A dark section on a light page has readable text. data-theme on an element other than <html> switched its tokens but not its text, which <body> had already worked out, so a data-theme="dark" section kept dark text on its own dark cards (1.14:1). The element now takes color: var(--text), at zero specificity, so text on its surfaces is 16.96:1. A colour your component sets still wins.
  • A chart's series 1 follows the chart's hue. --c1 was var(--brand-500), worked out on the root, so a .chart with its own --hue-brand had series 2 to 6 in the new hue and series 1 in the page's. --c1 is now the same oklch() recipe written out, so all six follow. At the root's hue it is the same colour as before.
  • Blurred chrome respects reduced transparency. Under prefers-reduced-transparency: reduce, .sticky-top, .form-actions-sticky, .tabbar, the carousel arrows (Deck's own and the native ::scroll-buttons) and .video-play now drop their blur and become their opaque --bg or --surface, as .glass already did. .modal::backdrop becomes solid, since without the blur the page would show through it sharp. A .sticky-top still in flow stays see-through. Without the setting nothing changes, pixel for pixel.

Added

  • Read, set and clear a combobox from script. el._deck.value() returns the selection (a string, or an array with data-multi), el._deck.value(v) replaces it with a value, an array, or { value, label } objects, and el._deck.clear() empties it. Both keep the <select> in step and fire change and deck:change as a pick does; { silent: true } skips the events.
  • Toggle buttons. A .btn with aria-pressed="true" now has a visible on state: the brand tint with a brand edge. A pressed .btn-soft fills solid, and the solid variants go a shade darker. aria-pressed is now Deck's attribute for "on"; the table at docs/components/button.php#on-attribute lists what each component accepts. Pages that already put aria-pressed on a .btn with their own pressed styles will see Deck's on state wherever their rules leave a property unset.
  • Seven theme-aware tokens. --surface-recessed is the recessed surface a control sits on: a progress, range or ring track, or a skeleton's sweep. --good-soft, --warn-soft and --bad-soft, each with a -text partner, are the status equivalents of --brand-soft and --brand-soft-text. Reach for these rather than the --ink-* and status ramps, which are the same colour in both themes.
  • Layout regression tests that render the built stylesheet in a real browser: npm test. They use Microsoft Edge by default; set DECK_TEST_BROWSER to chrome or to the path of a Chromium executable to use another. playwright-core is a dev dependency only; Deck still ships no runtime dependencies.
  • Ten icons. inbox and arrow-down-right, plus a media set: play, pause, skip, fullscreen, tv, film, music and broadcast, each in both cuts. The sprite is 86 icons, 174 symbols. docs/components/icon.php#set lists every icon in it.
  • Layout utilities. .ratio-2x3 for posters and book covers, .object-contain to letterbox instead of crop (it beats a .ratio-* frame's cover), .break-words (overflow-wrap: anywhere, which also stops a long string widening a flex item or table cell), and .z-sticky, .z-nav and .z-sheet, which put your own chrome on Deck's named stacking layers.
  • .input-sm and .input-lg, the heights of .btn-sm (34px) and .btn-lg (50px). They work on .select and inside .search too. Every control is still 16px on a touch screen.
  • .list-flush, a list with no border, radius or fill of its own, for a list inside a card or a sheet.
  • Dismissible alerts. A <button class="alert-close"> last in an .alert closes it: a 44px target that does not make the alert taller. deck.js hides the alert after a cancellable deck:dismiss event, and works for alerts inserted after the page loaded.
  • .grid-fill, the auto-fit grid with auto-fill, so a short last row keeps card-sized columns instead of stretching. It needs no .grid beside it.
  • .glass, a frosted, theme-aware surface for a card, panel or bar over an image: --surface at 72%, the backdrop blurred and saturated, a see-through border and a top-edge highlight that is weaker in dark mode. It sets no radius, border width or shadow, so a .card keeps its own. Three custom properties tune it, --glass-blur (16px), --glass-tint (72%) and --glass-edge (22%), and three classes set one each, so they combine: .glass-sm and .glass-lg (8px and 32px blur) and .glass-solid (90% tint, for muted text over a photo). Where backdrop-filter is not supported, and under prefers-reduced-transparency: reduce, it is the opaque surface with the component's own border.
  • .g-scrim-auto and --scrim-auto, a scrim that follows the theme, with --text over it: a light floor in a light theme, and exactly .g-scrim in a dark one, so switching to it changes nothing in dark mode. .g-scrim stays dark in both, for white text.
  • --scrim-strength, a multiplier on the scrim's opacity for .g-scrim, .g-scrim-auto and either with .g-scrim-top. The default is 1. Use less for a flat illustration, and about 1.3 under muted text, which falls below 4.5:1 over some photos at the default. While it is unset, each class paints its token, so a retuned --scrim still applies.
  • Deck::head(), Deck::css() and Deck::js() take a CSP nonce. Deck::head(nonce: $nonce) puts it on the stylesheet link, every script tag and the hue <style>, so the helper works under a strict Content Security Policy.
  • One region, its own brand: data-theme-scope. --hue-brand or --chroma-brand on an element now rethemes it when the element also has data-theme-scope. Deck declares its 18 brand tokens on :root and on [data-theme-scope], so a marked element works them out from its own hue: buttons, links, badges, focus rings, tints and chart series all follow, and scopes nest. Before, a hue below the root reached only gradients and five chart series, and the theming guide said so. Without the attribute that is still all it reaches, so a .g-mesh with an inline hue behaves as before. A --brand override on :root stops at a scope; set it on the scope to reach inside. Deck::theme(…, scope: true) emits the attribute beside the custom properties; without scope its output is unchanged. Deck.hue() on the root paints exactly as before.
  • Blur tokens. --blur-xs (3px, the modal backdrop), --blur-sm (8px, carousel arrows and .video-play), --blur-md (12px, .sticky-top and .form-actions-sticky) and --blur-lg (16px, .tabbar). Every blurred surface reads one, so retuning a strength is one change on :root. The values are the ones those surfaces already used, and each saturate() stays as it was. .glass keeps its own --glass-blur.
  • The docs can be searched for a component by what it is called. docs/components/index.php lists every public class under the page that documents it, generated from dist/api.json and the pages on each request, with the classes no page covers yet at the end. ?format=json returns the same index for a script or an AI assistant. The docs search now ranks pages by title, heading, description and the classes they claim, and treats "date picker", "datepicker" and .datepicker as one search. From the navbar it opens the page when one is clearly the answer, at the heading for the class: donut opens Charts at Donut. The old search showed a file and line for each class and never linked a page. "date picker" missed .datepicker, "upload" found nothing, and "ring" listed .alert-body first. llms.txt now points assistants at the index rather than at README's hand-kept component list.
  • Deck IntelliSense, an editor extension. Completion for classes, tokens and icon names, hover docs, and diagnostics for a class that is off a scale (.stack-5), removed, internal or one character from a real one, in Visual Studio Code, Cursor and Antigravity. Everything it knows is generated from the release it is built against. It is not on a marketplace: install deck-intellisense-0.1.1.vsix from the GitHub release or from tools/vscode/. docs/start/editor.php covers setup, every setting and troubleshooting.
  • Nineteen page templates in public_html/templates/, with a gallery page: a store, agency and local business sites, an app shell, settings, checkout, pricing, sign-in, and galleries of footers, sidebars, heroes and carousels. Each is a single HTML file with no build step. One is unfinished: dashboard.html has its top bar, sidebar and phone drawer, but its main area holds only the page title while it is rebuilt.

Changed

  • src/deck-icons.svg gained menu-book and menu-book-sm after 0.1.3. The brand artwork, which uses only the deck-mark symbol, did not change; src/brand/sources.json records the sprite's new hash so the build accepts it. menu-book is now listed in tools/icons/icons.txt as well, so regenerating the sprite keeps it.
  • Deck::head() no longer prints an inline <script> to set the sprite URL. Deck::js() puts it on the first script tag as data-deck-icons, which deck.js already read; the result is the same.
  • Deck.version is the whole version, '0.2.0'. From 0.1.0 to 0.1.4 it said '0.1', and so did the banners of deck.js, deck-extras.js and deck-adapters.js, because they are written by hand. The build now refuses to run when any of them, or Deck::VERSION, differs from package.json.
  • The Composer branch alias for dev-main is 0.2.x-dev.