Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .sync/PORTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,32 @@ material. Reproduce its *intent* in b24ui by editing files under `src/` only.
- **a11y** — keep the `axe` test case for the component green.
- **Tests** — for every new/changed prop, add a `renderEach` case so a snapshot
exercises it; update snapshots with `pnpm run test run -u` when markup changes.
- **A focus outline is coloured by `--ui-color-design-outline-focused-stroke`.**
Upstream colours focus from whatever accent is at hand; this fork has one
token for it. What makes it the right one is not that it is defined per
context — `accent-main-primary` and `accent-soft-element-blue` are too — but
that it *changes kind*: a saturated blue in the light and dark contexts, and
a translucent luminance shift in the edge ones, black at 35% over a light
surface and white at 40% over a dark one. `outline-primary` is the odd one
out for a different reason: it reads `--color-primary` from the Tailwind
theme block, which has no per-context override at all. Reapplying an
upstream hunk that reintroduces `outline-primary`,
`--ui-color-accent-soft-element-blue` or `--ui-color-accent-main-primary` on a
`focus-visible:outline-*` reverts an accessibility fix, not a preference: those
measure 1.99:1 on white and 2.22:1 on the dark background, against the 3:1 WCAG
2.2 SC 1.4.11 asks (#191). In the **edge** contexts the token is weaker than
what it replaced — it reaches about 2.4:1 there and cannot do better, because
a translucent shift over a backdrop the portal chooses has no guaranteed
ratio. That is a property of the token, tracked in #475, not a reason to paint
focus from an accent again. Guarded by
`test/utils/focus-accent-token.spec.ts`.

Out of scope, and deliberately: focus that reads `--b24ui-border-color` or
`--b24ui-background`, whether as `ring-` (inputs) or `outline-`
(breadcrumb, checkbox, switch, listbox, input-rating, radio-group). There the
stroke is the control's own border following the component palette, not a
focus accent.

- **Tag width caps are relative to the field, never an absolute length.**
Upstream's `tagsItemText` / `itemText` are plain `truncate` with no cap;
b24ui caps the **tag** at `max-w-[70%]` with `min-w-0`, marks the delete
Expand Down Expand Up @@ -791,3 +817,4 @@ forward, since every commit between the two would then never be judged.
- 2026-08-23 — closed #98 (PR #468) by adding the §6 rule **carry upstream's breaking marker into our subject**, and the matching §7 reviewer check. The issue's other two actions were already done and are recorded elsewhere: the version arithmetic table in `releasing.md` maps any `BREAKING CHANGE` or `!` to a major, with no v2-line exception, and release automation landed as release-please. What was missing is the one step automation cannot supply. release-please derives the bump from the squashed subject, so the marker is now load-bearing in a way it was not when the CHANGELOG was hand-written and a human read the diff — a dropped `!` used to be a cosmetic slip and is now a semver violation that every gate passes. The rule is stated in both directions on purpose, because a port is not a copy: a §2 divergence can absorb a break that upstream had, and this fork's own prop surface can break where upstream's did not. Last reviewed: 2026-08-23.
- 2026-08-23 — fix of #342 (PR #470): added the §2 rule **tag width caps are relative to the field**. `max-w-[180px]` on the tag label was ours — upstream is plain `truncate` — and it ellipsised tags that had room to spare, because a constant knows nothing about the field's width or what shares its row. Settled by measuring in Chromium rather than by reading the spec, which was the only way to tell three plausible readings apart: in a 562px field a tag wanting 590px renders at 199px under the old cap, 412px under `max-w-[70%]` with both tags still on one row, and 562px uncapped with the second tag pushed to the next line. The same measurement caught what review had flagged and reading had not — `input-tags.ts`'s root is `inline-flex` with no width, so the percentage was circular there: the field grew to 590px, overflowing its 562px parent, and clipped the label anyway. `max-w-full` on that root fixes both and is now part of the invariant. The guard went through three drafts, each corrected by mutation rather than by review: it credited one slot with a neighbour's class, then passed on its own comment (which contains the string `min-w-0`), then missed both the six per-size `tagsItem` overrides at deeper indentation and the `(prev) => [...]` slot form — an arrow function's parameter list closes before its body opens, so balancing brackets returned `(prev: string)` and nothing else. It ends at a comma at depth zero, and a self-check asserts the scanner matched something before reporting no offenders. Last reviewed: 2026-08-23.
- 2026-08-23 — added the §6 rule **name the upstream commit in the subject** and the §7 reviewer check, on the maintainer's request that ported commits be traceable from the changelog. Only the subject reaches `CHANGELOG.md`, so that is the only place the reference can go. Upstream's own first line was the request as originally put and is deliberately not what shipped: their `Slider` is this fork's `Range`, and §1 makes that rename mandatory, so copying their wording would name a component this library does not have — the reference points at the commit and the sentence stays about ours. Enforced by `assert-commit-parses.mjs`, which keys on a new entry appearing in `processed` rather than on the ledger being edited, so the reconciliation commits §6 step 4 requires are unaffected — verified against #467, which passes, and against #466 and #464, which are real ports and are flagged. The same pass extended that guard to reject a type with no `changelog-sections` entry (#437): a breaking commit of an unconfigured type keeps its raw lowercase type as the group title and sorts above every real section, reproduced by running the writer release-please uses. Both checks read `release-please-config.json` and the ledger rather than restating either. Last reviewed: 2026-08-23.
- 2026-08-23 — fix of #191 (PR pending): added the §2 rule **a focus outline is coloured by the focus token**, and took two design-agnostic fixes upstream had shipped alongside — `a:focus-visible { outline-offset: 0 }` in `index.css`, and `overflow: hidden` on every frame of the accordion and collapsible height animations. The issue read as an inconsistency, four colours doing one job across 46 theme files, and the maintainer scoped it to colour only: form stays per component, `ring-*` on inputs untouched. Measuring the four turned it into an accessibility fix. `outline-primary` is not a token at all — it resolves to `--color-primary`, a legacy Bitrix cyan at 1.99:1 against white — and `--ui-color-accent-soft-element-blue` is a dark blue in every context including the dark ones, 2.22:1 against `#262626`; WCAG 2.2 SC 1.4.11 asks 3:1, and seven of the nine outline-coloured sites failed it in at least one theme. `--ui-color-design-outline-focused-stroke` is the design system's own name for this and the only one of the four defined in all four contexts: 4.21:1 light, 4.24:1 dark. Two things were deliberately left: the `isAction` link's red focus, where the colour matches the hover state and the real defect is the token having no dark value (#473), and the fifteen `--b24ui-border-color` focus rings on inputs, where the ring is the field's border rather than a focus accent. A dead `hover:text(` — missing its dash, so Tailwind generated nothing — was removed from `link.ts` on the way past. Last reviewed: 2026-08-23.
2 changes: 1 addition & 1 deletion .sync/nuxt-ui.json
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@
"pr": 192,
"b24ui_sha": "6b11d5d4",
"decision": "skip",
"summary": "feat(theme): uniformize focus styles across components (#6576) — SKIPPED (maintainer call): rewrites focus styling across ~50 themes to nuxt/ui's outline-3/outline-{color}/25 + theme.colors model. b24ui has its own air-token focus system (ring-(--b24ui-border-color), --ui-color-design-selection-content, …) and no theme.colors; adopting upstream would regress the air design. Generic bits (a:focus-visible reset, keyframes overflow:hidden) also deferred. Tracked in issue #191"
"summary": "feat(theme): uniformize focus styles across components (#6576) — SKIPPED (maintainer call): rewrites focus styling across ~50 themes to nuxt/ui's outline-3/outline-{color}/25 + theme.colors model. b24ui has its own air-token focus system (ring-(--b24ui-border-color), --ui-color-design-selection-content, …) and no theme.colors; adopting upstream would regress the air design. The two generic bits were later taken on their own — a:focus-visible reset and keyframes overflow:hidden — along with a colour-only consolidation of the focus outline onto --ui-color-design-outline-focused-stroke, which turned out to fix contrast failures rather than only inconsistency (1.99:1 and 2.22:1 against 3:1). The upstream pattern itself stays skipped: form remains per component by maintainer decision. See issue #191."
},
"39d53ae54b9d810dc691c4fbe1ac6205df5f6e05": {
"pr": 193,
Expand Down
11 changes: 11 additions & 0 deletions src/runtime/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@
@variant web (&:where(html[data-platform="web"] *));

@layer base {
/*
* From upstream. Browsers give a focused link's outline the default offset,
* which on an inline link is measured from the text box and lands the ring
* over the line above or below in tight copy. Zero keeps it on the link.
* Only the offset — the colour, width and style stay with whatever rule the
* component's theme sets.
*/
a:focus-visible {
outline-offset: 0;
}

body,
.air-custom-bg,
/* @deprecate This rule (.sidebar-layout.--inner (not body !)) is deprecated and will be removed in version 3.0.0 */
Expand Down
15 changes: 15 additions & 0 deletions src/runtime/keyframes.css
Original file line number Diff line number Diff line change
@@ -1,39 +1,54 @@
/*
* `overflow: hidden` on every frame of the height animations, from upstream.
* Reka drives these by animating `height` from or to `0` while the content
* keeps its own height, so without it the content is drawn outside the
* collapsing box for the length of the animation — visible as a flash of text
* over whatever sits below the accordion.
*/
@keyframes accordion-up {
from {
overflow: hidden;
height: var(--reka-accordion-content-height);
}

to {
overflow: hidden;
height: 0;
}
}

@keyframes accordion-down {
from {
overflow: hidden;
height: 0;
}

to {
overflow: hidden;
height: var(--reka-accordion-content-height);
}
}

@keyframes collapsible-up {
from {
overflow: hidden;
height: var(--reka-collapsible-content-height);
}

to {
overflow: hidden;
height: 0;
}
}

@keyframes collapsible-down {
from {
overflow: hidden;
height: 0;
}

to {
overflow: hidden;
height: var(--reka-collapsible-content-height);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/theme/accordion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default {
'min-w-0 group flex-1 flex items-center gap-1.5',
'py-[12px]',
'font-(--ui-font-weight-medium) text-(length:--ui-font-size-sm) leading-[20px]',
'focus-visible:outline-(--ui-color-accent-soft-element-blue)',
'focus-visible:outline-(--ui-color-design-outline-focused-stroke)',
'cursor-pointer'
].join(' '),
content: 'motion-safe:data-[state=open]:animate-[accordion-down_200ms_var(--ease-out)] motion-safe:data-[state=closed]:animate-[accordion-up_200ms_var(--ease-out)] overflow-hidden focus:outline-none',
Expand Down
2 changes: 1 addition & 1 deletion src/theme/chat-reasoning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default {
'disabled:cursor-default',
'disabled:hover:text-muted',
'hover:text-default',
'focus-visible:outline-offset-2 focus-visible:outline-primary',
'focus-visible:outline-offset-2 focus-visible:outline-(--ui-color-design-outline-focused-stroke)',
'transition-colors'
].join(' '),
leading: 'relative size-5 shrink-0',
Expand Down
2 changes: 1 addition & 1 deletion src/theme/chat-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default {
'disabled:cursor-default',
'disabled:hover:text-muted',
'hover:text-default',
'focus-visible:outline-offset-2 focus-visible:outline-primary',
'focus-visible:outline-offset-2 focus-visible:outline-(--ui-color-design-outline-focused-stroke)',
'transition-colors'
].join(' '),
leading: 'relative shrink-0 size-5',
Expand Down
2 changes: 1 addition & 1 deletion src/theme/file-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export default {
},
compoundVariants: [
{
class: 'focus-visible:outline-inverted'
class: 'focus-visible:outline-(--ui-color-design-outline-focused-stroke)'
},
{
highlight: true,
Expand Down
2 changes: 1 addition & 1 deletion src/theme/footer-columns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default {
label: 'text-(length:--ui-font-size-lg)/[normal] font-(--ui-font-weight-semi-bold)',
list: 'mt-6 space-y-4',
item: 'relative',
link: 'group text-sm flex items-center gap-1.5 focus-visible:outline-primary',
link: 'group text-sm flex items-center gap-1.5 focus-visible:outline-(--ui-color-design-outline-focused-stroke)',
linkLeadingIcon: 'size-5 shrink-0',
linkLabel: 'truncate',
linkLabelExternalIcon: 'inline-block size-[14px] text-(--ui-color-design-plain-content-icon-secondary)'
Expand Down
4 changes: 2 additions & 2 deletions src/theme/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default (options: Required<ModuleOptions>) => ({
base: [
'cursor-pointer',
// 'focus-visible:outline-info-text' // fix
'focus-visible:outline-(--ui-color-accent-main-primary)',
'focus-visible:outline-(--ui-color-design-outline-focused-stroke)',
'focus-visible:outline-1 focus-visible:rounded-[4px]',
'text-start'
].join(' '),
Expand All @@ -37,7 +37,7 @@ export default (options: Required<ModuleOptions>) => ({
'border border-x-0 border-t-0 border-dashed',
'text-(--ui-color-design-outline-a1-content) border-b-(--ui-color-design-outline-a1-content)',
'hover:not-disabled:not-aria-disabled:no-underline',
'hover:text(--ui-color-accent-soft-element-red) hover:not-disabled:not-aria-disabled:text-(--ui-color-accent-soft-element-red) hover:border-b-(--ui-color-accent-soft-element-red)',
'hover:not-disabled:not-aria-disabled:text-(--ui-color-accent-soft-element-red) hover:border-b-(--ui-color-accent-soft-element-red)',
'focus-visible:outline-(--ui-color-accent-soft-element-red)'
].join(' ')
}
Expand Down
2 changes: 1 addition & 1 deletion src/theme/page-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default {
'group',
'text-(length:--ui-font-size-sm)',
'cursor-pointer',
'focus-visible:outline-(--ui-color-accent-main-primary)',
'focus-visible:outline-(--ui-color-design-outline-focused-stroke)',
'focus-visible:outline-1',
'focus-visible:rounded-[4px]',
'text-start',
Expand Down
2 changes: 1 addition & 1 deletion src/theme/splitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default {
panel: 'flex',
handle: [
'group relative shrink-0',
'outline-transparent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-color-accent-soft-element-blue)',
'outline-transparent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-color-design-outline-focused-stroke)',
'data-[panel-resize-handle-enabled=false]:cursor-default'
].join(' ')
},
Expand Down
4 changes: 2 additions & 2 deletions src/theme/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export default {
'isolate',
'[&>tr]:data-[selectable=true]:hover:bg-(--ui-color-bg-content-secondary)',
'light:[&>tr]:data-[selectable=true]:hover:bg-[#f6f8f9]',
'[&>tr]:data-[selectable=true]:focus-visible:outline-(--ui-color-accent-soft-element-blue)',
'[&>tr]:data-[selectable=true]:focus-visible:outline-(--ui-color-design-outline-focused-stroke)',
'[&>tr]:data-[selected=true]:hover:bg-(--ui-color-bg-content-secondary)',
'light:[&>tr]:data-[selected=true]:hover:bg-(#eff7d7)',
'[&>tr]:data-[selected=true]:focus-visible:outline-(--ui-color-accent-soft-element-blue)',
'[&>tr]:data-[selected=true]:focus-visible:outline-(--ui-color-design-outline-focused-stroke)',
'[&>tr]:last:[&>td]:border-b-0',
'divide-y divide-(--ui-color-divider-default)'
].join(' '),
Expand Down
2 changes: 1 addition & 1 deletion src/theme/toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default {
'relative group overflow-hidden',
'rounded-[8px] p-4',
'flex gap-4',
'focus-visible:outline-(length:--ui-design-outline-stroke-weight) focus-visible:outline-offset-2 focus-visible:outline-(--ui-color-design-outline-content-divider)',
'focus-visible:outline-(length:--ui-design-outline-stroke-weight) focus-visible:outline-offset-2 focus-visible:outline-(--ui-color-design-outline-focused-stroke)',
'font-[family-name:var(--ui-font-family-primary)]',
'bg-(--ui-color-base-black-fixed)/80',
'text-(--ui-color-design-plain-na-focused-content)',
Expand Down
Loading