dwui 0.10.0: visual overhaul and suite expansion - #26
Merged
Conversation
Replace the global *:focus{outline:none} reset with an opt-in focus
standard: dwui-focusable (outline-based :focus-visible ring) and
dwui-field-surface (:focus-within ring), both driven by the primary
palette. Every interactive component swaps its box-shadow ring + inline
outline suppression for the marker class; breadcrumb links, text_input,
select, and the date-picker field gain keyboard focus for the first time.
Add semantic tokens: --dwui-on-accent (color painted on primary-filled
controls) with a with_on_accent builder, tinted dwui-surface-* status
backgrounds via color-mix, and a dwui-scrim backdrop token. Remove the
now-dead dwui-ring-* utility maps.
Document the radius/height/padding/transition scales and add a
theme::layers z-index scale for upcoming overlay work.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The old labelled_rect_mixin drew its outline notch from four absolutely positioned divs sized by label.len() * 9px, which broke on proportional fonts and multi-byte labels, and its error message appeared and disappeared, shifting layout — text_input even shrank from h-10 to h-6 while invalid. The new mixins/field_mixin.rs provides field_surface_mixin (filled surface, dwui-field-surface :focus-within ring, state-colored underline, floating label positioned purely by transform) and field_error_row (an always-present message row with reserved height, role=alert only while it has a message). text_input, select, slider, and date_picker migrate onto it; select's render_function= oddity becomes render_fn=, the date-picker weekday header stops leaking the app's font-code class, and text_input/select/slider gain a disabled prop. Slider validation is now a real is_valid prop instead of hard-wired Valid. New form components sharing the system: text_area! (rows, resize, disabled), number_input! (native spinbutton semantics, themed non-tabbable steppers, min/max clamping), and radio_group! (WAI-ARIA radio pattern with roving tabindex; selection follows focus). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The slider was a bare native range input themed only by accent-color; it now gets a proper track and thumb via theme::controls, driven by a --dwui-slider-fill custom property so the filled portion of the track follows the value. Every vendor pseudo-element rule is inserted separately and -moz/-webkit rules are gated by user agent, because dominator panics when insertRule rejects a selector and each engine rejects the other's vendor pseudo-elements. The select drops its native chrome (appearance: none) for a themed chevron, the checkbox checkmark and switch knob switch from hard-coded white to the --dwui-on-accent token, and the switch knob now animates by transform instead of magic left offsets. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Card gains a default p-4 content padding (CardPadding::None restores the old look), loses the dead ColorScheme::Secondary variant, and completes the Primary scheme with light-mode and text colors. Heading renders the bare h1-h6 element — no wrapper div, no fixed h-12 — and finally takes its color from the theme. Modal's backdrop uses the dwui-scrim token instead of hard-coded rgba, its z-index comes from theme::layers, its sizes are rem-based, and it now traps Tab/Shift-Tab within the dialog. The trap helper is shared so the upcoming drawer can reuse it. Alerts get per-variant tinted surfaces (dwui-surface-*) instead of one shared background, and both the alert and modal dismiss buttons swap the literal × glyph for a stroke icon. Badge Error text uses the on-accent token and the Outline variant gets a real text color; tooltip offsets and z-index move onto the shared scales. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sweep: the tab underline becomes a real border-b-2 (the old inline inset box-shadow silently defeated focus rings), list rows use a spacing utility instead of an inline 10px pad, buttons drop the forced w-full and rounded-full for the shared rounded-md scale with an explicit filter/color transition, and spinner sizes move from hard-coded px to the rem sizing scale. The transition-all / bg-white / stroke-white grep-gate over components now comes back clean. New widgets: - pagination! — windowed page buttons (unit-tested pure window fn), aria-current, disabled edge arrows. - popover! — controlled anchored overlay primitive with Escape and click-outside dismissal. - dropdown_menu! — WAI-ARIA menu button: aria-haspopup/expanded, role=menu items, arrow/Home/End navigation skipping disabled items, Escape refocuses the trigger. - drawer! — modal side panel sharing the modal's scrim, Escape handling and focus trap, sliding in via new dwui-slide-in-* keyframes. - toasts! + Toaster — a cloneable handle pushing into an aria-live host above the modal layer; per-toast auto-dismiss timers ride the Dom future so they cancel on unmount. Adds gloo-timers and wasm-bindgen-futures to dwui. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The gallery gains cards for every new component — textarea & number input, radio group, dropdown menu, pagination, toasts, drawer — plus first-time cards for card/heading and the list component, which previously only existed as docs-sidebar chrome. The inputs card now demos the disabled state. Buttons no longer default to w-full, so the magnetic CTA wrappers on the home page opt back in via apply; section cards use CardPadding::Large instead of stacking their own padding. CHANGELOG documents the whole 0.10.0 overhaul. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
QA in a real Chrome session surfaced three issues the headless Firefox
suite could not:
- .style("-moz-appearance", ...) on the number input (and the redundant
-webkit-appearance on select) panics dominator in engines that reject
the property; plain appearance is universal now, so the prefixed
declarations are gone.
- position: fixed resolves against the nearest transformed ancestor, so
the modal, drawer, and toast host all broke inside the gallery's
route-transition wrapper (an identity transform is still a containing
block). They now render through utils::body_portal, which appends the
overlay to document.body for the lifetime of an invisible placeholder
and tears it down on unmount.
- The example app replaces <body> wholesale via replace_dom, so the
portal falls back to the document element when document.body is None.
The example app gains console_error_panic_hook so future panics surface
in the console, and the overlay tests query the document instead of the
test container, discarding their DOM handles to clean up the portals.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The raised label's line box reached the surface's top edge, colliding with the inset :focus-within outline. The label now uses a tight line-height and the field ring draws outside the surface with a 2px offset — the same geometry as the dwui-focusable ring on buttons — so the two can never overlap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The option popup is native chrome: it ignores utility classes on the element and takes its light/dark rendering from the element's color-scheme, so the dark-themed select was opening a light popup with light-gray text. The select now carries a dwui-select class with stylesheet rules setting color-scheme (dark by default, light under a .light ancestor) and explicit option background/text colors for engines that paint the popup themselves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI failed with "Failed to detect test as having been run" and not one failing assertion: the components suite never finished loading. Full debug info had pushed that test binary from 108 MB on main to 149 MB, and the wasm-bindgen runner only allows the page 20 seconds to fetch and instantiate the module — enough on a dev machine, not on a two-core runner. Line-tables-only debug info takes the binary to 28 MB, well under the pre-existing baseline, while panics keep pointing at real files and lines. CI also sets WASM_BINDGEN_TEST_TIMEOUT so future growth surfaces as a slow run rather than a phantom failure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The components suite never leaves "Loading scripts..." on the runner while passing locally against the runner's exact Chrome and chromedriver on two cores. Verbose driver logs and the machine's resources should show what the browser is actually doing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tailing the log only captured the polling loop. The interesting part is the session start and any page-level event. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This is what actually broke CI. wasm-bindgen 0.2.105 derives JS
identifiers for closure shims by sanitizing Rust symbol names, and two
distinct instantiations — invoke::<()> and another whose generics
sanitize away entirely — collapsed onto one name. The glue declared the
same function twice:
SyntaxError: Identifier
'wasm_bindgen_8081d5a46af9a2b0___convert__closures_____invoke______'
has already been declared
The module never parsed, so no test ever started and the harness page sat
at "Loading scripts..." until the runner gave up. Whether the collision
happens depends on the exact symbols the compiler emits: rustc 1.97.1
(CI) collides, 1.96.1 (my machine) does not, which is why every local
run passed. Installing 1.97.1 reproduced it exactly, and 0.2.126
generates a unique identifier per shim.
Bumping the crate requires wasm-bindgen-test 0.3.76 (it pins wasm-bindgen
exactly), which pulls web-sys 0.3.103, and the workflow's CLI pin has to
match the lockfile or the runner rejects the artifacts.
Corrects 35b4cb5, whose message blamed the binary size: at 28 MB with a
120s budget the failure was identical, so size was never the cause. That
commit's profile change is worth keeping on its own merits — a fifth of
the bytes to fetch and instantiate per run — so its comment now says so
instead. The raised WASM_BINDGEN_TEST_TIMEOUT stays as insurance against
a future suite genuinely outgrowing the default.
Verified on rustc 1.97.1 with CI's Chrome 150 and ChromeDriver 150:
Chrome 51+19, Firefox 51+19, clippy clean, native tests, and the example
app builds and loads without console errors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0.10.0 goes out alongside the dwind 0.8.0 work, and absorbs the dwui 0.9.1 changes documented below it — 0.9.1 was written up yesterday but never published. Also records the wasm-bindgen floor, since consumers on 0.2.105 can hit the duplicate-identifier collision. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A visual and accessibility overhaul of all 21 dwui components, plus eight new ones. Breaking where it needed to be — the gallery app migrates in the same series. See the
dwui 0.10.0CHANGELOG entry for the full details.Design-language foundation
*:focus { outline: none }reset with an opt-in, outline-based focus standard (dwui-focusable,dwui-field-surface). Text inputs, selects, sliders, the date-picker field, and breadcrumb links are keyboard-visible for the first time, and rings can no longer be defeated byshadow-*utilities.--dwui-on-accent, tinteddwui-surface-*status backgrounds, and adwui-scrimbackdrop — all hard-coded whites/blacks now follow the theme.theme::layersz-index scale.Field system
label.len() * 9px) is replaced byfield_surface_mixin+field_error_row: transform-only floating label, always-reserved error row, no layout shift on validation. Invalid inputs no longer shrink.text_input/select/slidergaindisabled; slider gains a realis_valid; the select's native option popup is theme-colored viacolor-scheme.Control chrome & surfaces
insertRulecan never panic either engine), themed select chevron, on-accent checkbox check and switch knob.New components
text_area!,number_input!,radio_group!,pagination!,popover!,dropdown_menu!,drawer!, andtoasts!/Toaster— full ARIA/keyboard patterns, all in the gallery.Overlay portals
modal!,drawer!, andtoasts!render through a newutils::body_portal:position: fixedresolves against the nearest transformed ancestor, and the gallery's route transition was capturing overlays. Falls back to the document element for apps that replace<body>wholesale.Test plan
wasm-pack test --headless --firefox crates/dwui: 51 component + 19 styling browser tests, all passing.cargo test -p dwui: 17 native unit tests.🤖 Generated with Claude Code