Skip to content
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
15030fc
feat(theme): add the focus colour and accessibility modifiers
alexgrozav Oct 29, 2026
20f311d
fix(theme): fall back to the primary colour when color.focus is absent
alexgrozav Oct 29, 2026
12ff89d
Merge focus-modifiers-uxf-491 into align-component-styles-uxf-487
alexgrozav Oct 29, 2026
3753f63
fix(theme): align the button recipe with Open Components
alexgrozav Oct 29, 2026
bed2ed6
feat(theme): align the card recipes with Open Components
alexgrozav Oct 29, 2026
90820fa
feat(theme): align the input recipes with the Open Components Input page
alexgrozav Oct 29, 2026
fb3a507
fix(theme): align the modal recipes with the Dialog standard
alexgrozav Oct 29, 2026
88caf6b
fix(theme): name the more-contrast border overrides through the instance
alexgrozav Oct 29, 2026
7155b71
feat(theme): align the popover recipes with Open Components
alexgrozav Oct 29, 2026
68f64df
feat(theme): align the spinner recipes with Open Components
alexgrozav Oct 29, 2026
5f62ea9
fix(theme): align the radio recipes with the Radio Group standard
alexgrozav Oct 29, 2026
4ceffac
feat(theme): align the switch recipes with the Open Components Switch…
alexgrozav Oct 29, 2026
86fe016
fix(theme): keep the touch text size to the input and correct the fie…
alexgrozav Oct 29, 2026
0831d4d
fix(theme): wrap the modal footer's actions on narrow screens
alexgrozav Oct 29, 2026
c3d1314
fix(theme): reset the top-layer popover padding and tighten its tests
alexgrozav Oct 29, 2026
d50b9b4
fix(theme): style the expanded state and widen small buttons
alexgrozav Oct 29, 2026
444cd76
fix(theme): keep spinner colors and labels readable on what they sit on
alexgrozav Oct 29, 2026
e3ff37d
fix(theme): align the checkbox recipes with Open Components
alexgrozav Oct 29, 2026
843fd02
feat(theme): align the tooltip recipes with Open Components
alexgrozav Oct 29, 2026
6d15a31
fix(theme): fall back to the primary colour for the card focus ring
alexgrozav Oct 29, 2026
4a53f4d
docs(docs): reword the radio focus sentence so it doesn't open with code
alexgrozav Oct 29, 2026
dea6a2b
fix(theme): keep the disabled switch label at full opacity in forced …
alexgrozav Oct 29, 2026
aa38dd3
docs(theme): say the touch hit area is at least 44px tall
alexgrozav Oct 29, 2026
e3113b7
docs(theme): name the modal section recipes in the changeset
alexgrozav Oct 29, 2026
467e7fb
Merge popover-open-components-uxf-497 into align-component-styles-uxf…
alexgrozav Oct 29, 2026
8b893ab
Merge spinner-recipes-uxf-499 into align-component-styles-uxf-487
alexgrozav Oct 29, 2026
2c83d31
fix(theme): keep disabled buttons interactive and borders at 3:1
alexgrozav Oct 29, 2026
9635135
test(theme): pin the checkbox forced-colors blocks and hit area
alexgrozav Oct 29, 2026
d4bd325
fix(theme): pin every side of the tooltip arrow in its tests
alexgrozav Oct 29, 2026
3521fa4
Merge checkbox-styles-uxf-494 into align-component-styles-uxf-487
alexgrozav Oct 29, 2026
3da18ee
Merge modal-dialog-uxf-495 into align-component-styles-uxf-487
alexgrozav Oct 29, 2026
5b9b395
Merge align-input-recipes-uxf-496 into align-component-styles-uxf-487
alexgrozav Oct 29, 2026
b2f39a6
Merge align-switch-recipes-uxf-500 into align-component-styles-uxf-487
alexgrozav Oct 29, 2026
7e93426
fix(theme): keep focused aria-disabled buttons still when pressed
alexgrozav Oct 29, 2026
19d445c
Merge radio-group-standard-uxf-498 into align-component-styles-uxf-487
alexgrozav Oct 29, 2026
cd43c2a
Merge align-tooltip-styles-uxf-501 into align-component-styles-uxf-487
alexgrozav Oct 29, 2026
b8b2e2d
fix(theme): round the card media inside the border and keep positione…
alexgrozav Oct 29, 2026
bdc21fd
Merge align-button-uxf-492 into align-component-styles-uxf-487
alexgrozav Oct 29, 2026
6361b01
Merge align-card-recipes-uxf-493 into align-component-styles-uxf-487
alexgrozav Oct 29, 2026
9815054
fix(theme): align the checkbox group recipe with the Checkbox Group s…
alexgrozav Oct 29, 2026
94842e9
docs(theme): reword the checkbox group contrast note
alexgrozav Oct 29, 2026
746177a
feat(theme): align the textarea recipe with Open Components
alexgrozav Oct 29, 2026
239c43e
fix(theme): keep checkbox group parts together and disabled boxes in …
alexgrozav Oct 29, 2026
c702242
fix(theme): give the textarea itself the touch height and resize it o…
alexgrozav Oct 29, 2026
c38bd3b
Merge align-textarea-recipes-uxf-503 into align-component-styles-uxf-487
alexgrozav Oct 29, 2026
e18ba07
Merge checkbox-group-styles-uxf-502 into align-component-styles-uxf-487
alexgrozav Oct 29, 2026
cf0760f
fix(theme): limit the global link hover to devices that can hover
alexgrozav Oct 30, 2026
92165b3
Merge limit-link-hover-uxf-508 into align-component-styles-uxf-487
alexgrozav Oct 30, 2026
1b15851
fix(plugin): register the spinner recipes in the playground
alexgrozav Oct 30, 2026
5ae4006
docs(theme): correct the input, textarea and popover changesets
alexgrozav Oct 30, 2026
8cb9e89
docs(docs): match the field, otp, modal and textarea pages to the rec…
alexgrozav Oct 30, 2026
4dcd710
fix(storybook): give the focus color its swatch
alexgrozav Oct 30, 2026
3ea5ef4
docs(theme): keep the input date's touch-screen exception in the inpu…
alexgrozav Oct 30, 2026
ea80cfe
test(plugin): skip type-only specifiers in the rendered-component check
alexgrozav Oct 30, 2026
183538f
Merge fix-integration-blockers-uxf-519 into align-component-styles-ux…
alexgrozav Oct 30, 2026
8ffb267
fix(theme): rename the spinner's em size to inherit and keep light fixed
alexgrozav Oct 30, 2026
ffc78d6
fix(theme): keep light and dark fields and overlays fixed across themes
alexgrozav Oct 30, 2026
2fd19c7
docs(theme): start the spinner's fixed-colour sentence with words
alexgrozav Oct 30, 2026
88f1cde
fix(theme): keep the neutral input date separator and guard field bor…
alexgrozav Oct 30, 2026
12c3542
Merge spinner-inherit-light-uxf-523 into align-component-styles-uxf-487
alexgrozav Oct 30, 2026
1abc3ef
Merge light-dark-fixed-uxf-524 into align-component-styles-uxf-487
alexgrozav Oct 30, 2026
c0f7017
fix(theme): apply the neutral input date separator in the dark theme
alexgrozav Oct 30, 2026
ec8f251
docs(docs): note the neutral soft drawer's dark-theme edge
alexgrozav Oct 30, 2026
8ece442
fix(storybook): show light and dark field previews on their own surfaces
alexgrozav Oct 30, 2026
d9032c0
Merge preview-surfaces-uxf-527 into align-component-styles-uxf-487
alexgrozav Oct 30, 2026
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
5 changes: 5 additions & 0 deletions .changeset/align-button-uxf-492.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": patch
---

Make `useButtonRecipe` meet the Open Components Button standard's stylesheet rules. Every label now keeps 4.5:1 contrast at rest, on hover, when active, pressed or expanded, in both themes, so `solid` buttons darken on hover instead of lightening, apart from `warning`, which takes a dark label and still lightens, and the transparent variants use lighter levels in the dark theme. Every border keeps 3:1 against the surface it's meant for, so `subtle` borders and the `neutral` borders are darker in the light theme and lighter in the dark theme, `warning` `outline` borders are darker, and so is the light theme's `warning` `solid` border. Hover also sets the label color, so on devices that can hover, a button rendered as `<a href>` keeps it over the global link hover styles. The focus ring uses `color.focus`, the `link` variant is underlined at rest, hover styles only apply on devices that can hover, and the `&:focus` styles that copied the hover background are gone, since the focus ring shows focus. Disabled, pressed, expanded and loading are styled from `aria-disabled`, `aria-pressed`, `aria-expanded` and `data-loading` as well as `:disabled`. Disabled buttons no longer set `pointer-events: none`, so their cursor shows and clicks don't fall through, and they keep their resting colors on hover and press. Forced colors mode uses system colors for these states, and `prefers-contrast: more` draws `outline` and `subtle` borders at full strength. Each size sets a `min-height` and a matching `min-width`, labels wrap instead of staying on one line, horizontal padding is `padding-inline`, and touch screens get a 44 × 44px hit area. The `light` color is meant for dark surfaces and `dark` for light surfaces, and their `link` hovers no longer turn the text invisible. No color, variant, size or default changes.
5 changes: 5 additions & 0 deletions .changeset/align-card-recipes-uxf-493.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": minor
---

Add a `useCardMediaRecipe` for an image at the top of a card, and make the card recipes meet the Open Components Card standard. A link in a heading inside the card header now stretches over the whole card, actions in the card stay clickable above it, and the card shows a 2px `color.focus` ring outside its border when that link has keyboard focus, through `useCardRecipe`'s setup selectors. Hover strengthens the border and underlines the heading only inside `@media (hover: hover)`, the border goes to full strength with `prefers-contrast: more` and stays a system colour in forced colours mode. The card no longer sets `overflow: hidden`, so content and the focus ring are never clipped, and it wraps long words with `overflow-wrap: break-word`. `useCardHeaderRecipe`, `useCardBodyRecipe` and `useCardFooterRecipe` pad their inline sides with `paddingInline` instead of `paddingLeft` and `paddingRight`, so cards mirror in right-to-left layouts. The media part shows above the header with `order: -1` and keeps a `16 / 9` aspect ratio. Every recipe name, axis, value and default stays the same.
5 changes: 5 additions & 0 deletions .changeset/align-input-recipes-uxf-496.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": patch
---

Bring `useInputRecipe`, `useInputPrefixRecipe` and `useInputSuffixRecipe` in line with the Open Components Input standard. The focus ring is `@color.focus` in every state, and an invalid field shows through its error border and a new 1px inset shadow instead of a red ring. The wrapper now also takes its invalid, read-only and disabled look from the nested input's `aria-invalid="true"`, `readonly` and `disabled` attributes. Hover styles only apply on devices that can hover, each size has a `min-height`, and on touch screens the input's text is at least 16px and its hit area at least 44px tall. Disabled fields turn `GrayText` in forced colors mode, and borders use the text color with `prefers-contrast: more`. The placeholder, prefix and suffix are now the field's text color at 75%, and the prefix and suffix use logical padding. The read-only state no longer tints the fill, and `light` and `dark` ghost fields take their own fill on a page of the other mode, so their text keeps 4.5:1. `useTextareaRecipe` and `useInputDateRecipe` share the field wrapper, so they get the same changes except the touch-screen text size and hit area, and `useTextareaPrefixRecipe` and `useTextareaSuffixRecipe` get the prefix and suffix changes. `useOtpCellRecipe` shares only the surface colors and states: its cells get the hover, invalid, disabled and ghost changes, and keep their own `@color.primary` focus ring.
5 changes: 5 additions & 0 deletions .changeset/align-switch-recipes-uxf-500.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": patch
---

Bring `useSwitchRecipe` and `useSwitchFieldRecipe` in line with the Open Components Switch standard. Every off track now keeps 3:1 against the page in light and dark mode, with a knob at 3:1 against it: `neutral` is `gray-450` in both modes, `light` is a `gray-200` track with a `gray-500` border and a `gray-600` knob, and `dark` gets a `gray-450` border. The focus ring is `@color.focus`, and the field takes an invalid look from `aria-invalid="true"` (an error border doubled by an inset shadow) and a read-only look from `aria-readonly="true"` (a dashed border). Sizes are in `rem`, the knob is a gradient that moves to the inline end, so it mirrors in right-to-left layouts, and it jumps rather than slides with reduced motion. The wrapper label is at least 24px tall, and 44px on touch screens, so it gives the switch a large enough hit area. In forced colors mode the switch draws itself in system colors, and with `prefers-contrast: more` the off border takes the text color.
5 changes: 5 additions & 0 deletions .changeset/align-textarea-recipes-uxf-503.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": minor
---

Bring `useTextareaRecipe` in line with the Open Components Textarea standard, and add an `autogrow` axis that defaults to `false`, so today's output stays the same. With `autogrow: true`, in browsers that support `field-sizing`, the textarea grows with its text through `field-sizing: content` and has no resize handle, up to the rows in its `data-max-rows` attribute. The nested textarea now has a `min-height` of its `rows` in lines, read with `attr()`, so resizing or growing never makes it shorter than its rows. On touch screens its text is at least 16px, so Safari on iOS doesn't zoom in on focus, and the textarea is at least 44px tall. The `vertical` resize default now also sets `resize: block`, where browsers support it, so it follows the writing mode. `useTextareaPrefixRecipe` and `useTextareaSuffixRecipe` are unchanged.
5 changes: 5 additions & 0 deletions .changeset/align-tooltip-styles-uxf-501.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": minor
---

Align the tooltip recipes with the Open Components Tooltip standard. `useTooltipRecipe` and `useTooltipArrowRecipe` gain a `placement` axis (`top`, `bottom`, `start` or `end`), which defaults to `top`, the side the arrow pointed from before. On an element with the `popover` attribute, `useTooltipRecipe` now opens the tooltip in the top layer with `position: fixed`, `inset: auto` and the new `@tooltip.offset` variable as its margin, sets `position-area` from `placement` and flips it with `position-try-fallbacks: flip-block, flip-inline`. It fades and grows in as it opens, and only fades with `prefers-reduced-motion: reduce`. Every other element keeps its own positioning and the `@z-index.tooltip` layer, so tooltips you position yourself look the same. The content now wraps at `32ch` rather than `240px`, breaks long words with `overflow-wrap: anywhere`, and takes `Canvas` and a `CanvasText` border in forced colours mode. `useTooltipArrowRecipe` now draws its triangle in `color`, turns with `placement`, uses logical properties so it mirrors in right-to-left layouts, sits on the edge that faces the trigger inside a top-layer tooltip, and hides in forced colours mode. CSS can't tell which side a fallback chose, so when a tooltip flips, set the arrow's `placement` from a script.
5 changes: 5 additions & 0 deletions .changeset/checkbox-group-styles-uxf-502.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": patch
---

Make `useCheckboxGroupRecipe` meet the Open Components Checkbox Group page. The group now resets its `<fieldset>` and styles its `<legend>` at the options' text size in a heavier weight, and dims the legend of a disabled fieldset, in `GrayText` in forced colors mode. A horizontal group puts 24px between its options, the size axis sets the row gap with `gap-y` rather than `gap`, and options in `[data-slot="options"]` after a `[data-slot="select-all"]` box sit in from it with `padding-inline-start`, so their boxes line up with its label. The group's `aria-invalid="true"` gives every box the checkbox's invalid border. Each size adds a `-size-<size>` class to the group.
5 changes: 5 additions & 0 deletions .changeset/checkbox-styles-uxf-494.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": patch
---

Bring `useCheckboxRecipe` and `useCheckboxFieldRecipe` in line with the Open Components Checkbox standard. The focus ring now uses `@color.focus`, and the unchecked border is `@color.gray-450` on light surfaces and `@color.gray-500` on dark ones, up from `gray-300` and `gray-600`, so it has 3:1 against the page. The box is sized in `rem`, the wrapper draws a `1.5rem` hit area around it and pads each label to that height, and a wrapping label keeps the box beside its first line, in either direction. The field styles `[aria-readonly="true"]` with a dashed border and `[aria-invalid="true"]` with an `@color.error` border and a second inset pixel, and never `:read-only` or `:invalid`. With `prefers-contrast: more` the border is drawn at full strength, and in forced colors mode a checked box is filled with `Highlight`, its mark gets a dark outline and a disabled box shows `GrayText` instead of an opacity. A disabled box inside the wrapper no longer dims twice. Variant names, values and defaults are unchanged.
5 changes: 5 additions & 0 deletions .changeset/focus-modifiers-uxf-491.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": minor
---

Add a `color.focus` token for focus rings, plus five accessibility modifiers and a `position-try-fallbacks` utility. The token is a reference to `@color.primary` in the light and dark themes, emitted as `--color--focus`, and the `focus.outline-color` default now points at it, so focus rings look the same as before. When a project replaces the default colors and defines no `focus` color, the default falls back to `@color.primary`, as before. The new modifiers are `hover-capable` (`@media (hover: hover)`) and `any-pointer-coarse` (`@media (any-pointer: coarse)`) from `useMediaPreferenceModifiers`, `aria-invalid` (`[aria-invalid="true"]`) from `useAriaStateModifiers`, `readonly-attr` (`[readonly]`) from `useFormStateModifiers` and `data-loading` (`[data-loading]`) from `useOtherStateModifiers`, each with its own composable, like `useHoverCapableModifier`. The `usePositionTryFallbacksUtility` composable is registered in `useUtilitiesPreset` as `createPositionTryFallbacksUtility` and ships without defaults. Reference colors like `focus` no longer get level, shade or tint keys in the `useDesignTokensPreset` return type, because none are generated for them.
5 changes: 5 additions & 0 deletions .changeset/limit-link-hover-uxf-508.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": patch
---

Apply the hover styles of `useLinkElement` only on devices that can hover. The `a:hover` color and text decoration now sit inside `@media (hover: hover)`, so a tap on a touch screen no longer leaves a link, or a recipe rendered as `<a href>` like a Button, looking hovered. The `link.hover.color` and `link.hover.text-decoration` variables, their defaults and the `useLinkElement` options stay the same.
5 changes: 5 additions & 0 deletions .changeset/modal-dialog-uxf-495.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": patch
---

Align the Modal recipes with the Open Components Dialog standard. The `useModalRecipe` panel now stays `1rem` inside each edge of the screen and caps its width per size at `24rem`, `32rem` or `48rem`, unless it's `fullscreen`, so you can drop any `max-width` you set yourself. It wraps long titles, keeps its border visible in forced colors mode and at full strength with `prefers-contrast: more`, and stops the page scrolling while a `modal-overlay` is on it, so render the overlay only while the modal is open. In `useModalHeaderRecipe`, `useModalBodyRecipe` and `useModalFooterRecipe`, the body scrolls while the header and footer keep their height, the footer's actions wrap on a narrow screen, and the padding uses logical properties, so they mirror in right-to-left languages. The drawers share these section recipes and the `soft` surfaces, so `useDrawerHeaderRecipe`, `useDrawerBodyRecipe`, `useDrawerFooterRecipe` and `useDrawerRecipe` inherit the logical padding, the wrapping footer and a `soft` border in the dark theme.
5 changes: 5 additions & 0 deletions .changeset/popover-open-components-uxf-497.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": minor
---

Align the popover recipes with the Open Components Popover standard. `usePopoverRecipe` and `usePopoverArrowRecipe` gain a `placement` axis (`top`, `bottom`, `start` or `end`), which defaults to `top`, the side the arrow pointed from before. On an element with the `popover` attribute, `usePopoverRecipe` now opens the popover in the top layer with `position: fixed`, `inset: auto`, no padding from the browser's own `[popover]` styles, and the new `@popover.offset` variable as its margin, sets `position-area` from `placement`, flips it with `position-try-fallbacks`, and registers the `--popover-fit`, `--popover-fit-below` and `--popover-fit-above` `@position-try` rules. It fades in only without a reduced-motion preference. Every other element keeps `position: relative`, so popovers you position yourself look the same. The container is never wider or taller than the screen less `@spacing.xl`, wraps long words, and draws a full-strength edge under `prefers-contrast: more` and a `CanvasText` edge in forced colours mode. `usePopoverBodyRecipe` scrolls when the popover can't grow, and `usePopoverHeaderRecipe` drops its top separator after a leading arrow, as it does for a first child. `usePopoverArrowRecipe` now draws its triangle in `color`, turns with `placement`, uses logical properties so it mirrors in right-to-left layouts, sits on the edge that faces the anchor inside a top-layer popover, hides in forced colours mode, and sets `@popover.arrow.size` to `0.375rem` (6px at the default text size).
5 changes: 5 additions & 0 deletions .changeset/radio-group-standard-uxf-498.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": patch
---

Bring `useRadioRecipe`, `useRadioFieldRecipe` and `useRadioGroupRecipe` in line with the Open Components Radio Group standard. The focus ring is `@color.focus`, and the unchecked rings are now `@color.gray-450` on light surfaces and `@color.gray-500` on dark ones, so they keep 3:1 against the page and the fill in both modes. Each radio row has a `min-height` in `rem` (`2rem`, `2.25rem` and `2.5rem`, the standard's Input heights), and at least 44px on touch screens, and the circle is sized in `rem` with no margin of its own. In forced colors mode the browser draws its own radio, with a `Highlight` focus ring and a `GrayText` disabled label, and with `prefers-contrast: more` the rings are drawn at full strength. The group now resets its `<fieldset>`, styles its `<legend>`, a `data-slot="hint"` and a `data-slot="error"`, and turns every ring red, with an inset shadow, while it has `aria-invalid="true"`.
5 changes: 5 additions & 0 deletions .changeset/spinner-recipes-uxf-499.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@styleframe/theme": minor
---

Align the Spinner recipes with the Open Components Spinner standard. `useSpinnerRecipe`, `useSpinnerCircleRecipe` and `useSpinnerTextRecipe` gain an `em` size, which makes the spinner `1em` wide and tall so it matches the text around it, and the spinner no longer shrinks in a flex row. With reduced motion, `useSpinnerCircleRecipe` swaps the turn for a pulse that fades the spinner to 40% and back every 1.6 seconds, and stops the arc from growing and shrinking. The pulse uses `!important` for its duration and iteration count, so it keeps going under the sanitize preset. The spinner has no background of its own, so in `useSpinnerRecipe` the `light` color now switches to `@color.white` in the dark theme, where a `gray-700` spinner was nearly invisible, and `primary` switches to `@color.primary-400`, so a label reaches 4.5:1 on the dark page. The `dark` color stays white in both themes, for dark surfaces, and `useSpinnerOverlayRecipe` darkens its backdrop from 50% to 75% black, so a `dark` label on it reaches 4.5:1 too. With the `em` size, the spinner also sits `0.125em` below the baseline, so it centres on the text. `useSpinnerCircleRecipe` now uses the `motion-reduce` modifier, so register it first, as `useModifiersPreset` does.
Loading
Loading