Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
a247084
fix(sonar): the outlined preset page and tsconfig.json parse again
alessandrobellesia Oct 7, 2026
2c74864
Merge pull request #126 from volverjs/fix/sonar-parse-errors
alessandrobellesia Oct 8, 2026
fd1a59e
fix(utilities, dropdown): five defects found by the docs review
alessandrobellesia Oct 8, 2026
fa69ad5
Merge pull request #127 from volverjs/feature/missing-components
alessandrobellesia Oct 12, 2026
f26e2d5
fix(utilities): a nested map takes the current breakpoint alone
alessandrobellesia Oct 12, 2026
1c5d64b
feat(utilities): container queries
alessandrobellesia Oct 12, 2026
232cc60
feat(components): open and close transitions for accordion, popover a…
alessandrobellesia Oct 12, 2026
b549dca
feat(props): the radius scale follows --rounded
alessandrobellesia Oct 12, 2026
bd035e2
docs: llms.txt and llms-full.txt, and a sitemap that keeps /style
alessandrobellesia Oct 12, 2026
19b8255
refactor(scripts): node: imports, and the llms script without the Son…
alessandrobellesia Oct 12, 2026
acfe180
docs(utilities): the callout of a utility page shows container querie…
alessandrobellesia Oct 12, 2026
aeea984
Merge pull request #128 from volverjs/feature/modern-css
alessandrobellesia Oct 12, 2026
0741327
docs(changelog): cut the 0.1.31 release section
alessandrobellesia Oct 12, 2026
ca1be68
docs(colors): the swatch of the readable check lines up with the fields
alessandrobellesia Oct 12, 2026
3d4a309
Merge pull request #130 from volverjs/fix/readable-check-swatch
alessandrobellesia Oct 12, 2026
31933ed
docs(changelog): the swatch fix ships in 0.1.31
alessandrobellesia Oct 12, 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
2 changes: 2 additions & 0 deletions .eslintrc-auto-import.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"WritableComputedRef": true,
"asyncComputed": true,
"autoResetRef": true,
"componentSections": true,
"computed": true,
"computedAsync": true,
"computedEager": true,
Expand Down Expand Up @@ -301,6 +302,7 @@
"useWindowScroll": true,
"useWindowSize": true,
"utilityColors": true,
"utilityGroups": true,
"watch": true,
"watchArray": true,
"watchAtMost": true,
Expand Down
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@

All notable changes to this project will be documented in this file.

## [0.1.31] - 2026-10-12

### Added

* Container queries. The `@container` class sets `container-type: inline-size`, and the utilities that lay a block out take a container prefix, the name of a step after an `@`: `@md:flex-row` applies when the nearest `@container` above the element is at least 28rem wide, and never when there is none. The steps are the ones of Tailwind CSS, `@xs:` 20rem, `@sm:` 24rem, `@md:` 28rem, `@lg:` 32rem, `@xl:` 36rem, `@xxl:` 42rem and `@xxxl:` 48rem, set by `$container-breakpoints`, and they reach display, flex direction and wrap, the justify and align utilities of content and items, `grid-cols-`, `col-` and text align: 70 classes per step, 2 KB of `volver.css` once gzipped, where giving them to every responsive utility would have added about 570 KB before compression. A container class is emitted after the responsive classes of its utility, so it wins when both apply. `spread-map-into-utilities` takes the steps in a `$cqs` argument, `$container-breakpoints: ()` emits none, and `cq-up`, a shorthand for `container-breakpoint-up`, wraps any rule in the container query of a step; an unknown step, or one of 0, which would emit the base classes a second time after the responsive ones, stops the compilation. Containing the inline size stops an element from taking its width from its content, so `@container` belongs on a block that fills its parent, a card or a column, and collapses an `inline-flex` or a flex item with no basis to nothing. For the same reason no component turns itself into a container.
* Open and close transitions on the native elements, with no script. The content of `vv-accordion` grows from 0 to its height and back: its `::details-content` transitions `block-size` to `auto` through `interpolate-size`, and `content-visibility` with `allow-discrete`, so the content stays painted until the closing ends. A browser without them, Safari and Firefox today, opens it at once as before. A `vv-popover` with `popover` fades in from a `@starting-style` and fades out with `display` and `overlay` held by `allow-discrete` until the fade ends, where the browser holds them: Safari 17.6 fades it in and hides it at once, as before. One shown without `popover` does not fade. The closed panel takes its opacity of 0 from `[popover]:not(:popover-open)`, which a browser without the Popover API (Safari 16) drops: there the element is shown as it is, and an opacity on `[popover]` alone would have left it invisible and still in the way of the pointer. A `vv-dialog` opened with `showModal()` fades in and closes at once. A fade out would need `allow-discrete` on the closed dialog, and `@volverjs/ui-vue` hides its dialog with an inline `display: none` and closes it in the same task: the dialog would stay on the page for 300ms after every close, invisible and in the way of the pointer. The Vue transitions of the dialog run as before. Every duration reads the duration tokens, which are 0s under reduced motion.
* `details-content` is a pseudo-element a component map can style. It was missing from the list the generator checks, which dropped the rule and still declared its custom properties, and it gets no `content: ''` of its own: it wraps content of the page instead of generating a box.
* `llms.txt` and `llms-full.txt` on the documentation site, in the format of llmstxt.org. The index links the agent skill and every page of the styleguide with its description; the full file is the skill, the overview and its four references, which is written for a model and checked against the compiled CSS, while the pages are built from live examples that read as noise once flattened. The skill is published next to them, under `skills/volverjs-style/`, so the links between its files keep working.

### Changed

* `--rounded` is the base of the radius scale. `--rounded-sm` to `--rounded-xxxl` are declared as `calc(var(--rounded) * <ratio>)`, the ratio taken from `$border-radius` at compile time, so the computed values do not move while `:root, :host, .theme { --rounded: 0.375rem }` now rescales every corner of the utilities and of the components, where before it changed the `rounded` step alone. The steps are computed where they are declared, on the root and on every `.theme`, so a section with the `theme` class can carry a scale of its own. `none` and `full` keep their value, as does any step listed in the new `$border-radius-fixed` or whose unit differs from the one of `rounded`; a pill in px over a base in px has to be listed there. A script that reads `--rounded-lg` through `getPropertyValue` gets the `calc()` instead of `0.5rem`.

### Fixed

* `.container` was 0px wide on a viewport narrower than 360px, with its content spilling out of it. Its `max-width` reads `--breakpoint-value`, which the props set at each breakpoint to the width it starts at, and the first breakpoint, `xxs`, starts at 0. The token keeps that value, which is true of the breakpoint and is what a script reading it gets. Instead, the container rule now takes the whole width up to the next breakpoint, through a media query for every breakpoint that starts at 0. In `dist/` the `.container` rule gains `@media (width<=359.98px)` with `max-width: 100%`, and nothing else moves.
* The sizing classes walked into the dynamic spacing map, which holds the value of each size at each breakpoint, and emitted a class per entry: `.w-xs-xxs`, `.h-md-sm`, `.max-w-xl-lg` and so on, for `w-`, `h-`, `max-w-`, `max-h-`, `min-w-` and `min-h-`. Each read a token nothing declares, with a unitless fallback (`var(--spacing-xs-xxs, 6)`), which is invalid, so the class reset the property instead of setting it. Only `sm` and `lg` came out with a bare name, because they are also the names of two breakpoints: `.w-sm` and `.w-lg` read the right token, still with the unitless fallback. The recursion also ran the loop over the breakpoints again for every breakpoint, so about half of the rules were duplicates. The classes now read the dynamic tokens the way the spacing, gap and inset utilities do, with one class per size, from `w-xs` to `w-xl` for each of the six prefixes and their responsive variants, and the token resolves the size for the viewport. `max-w-md` is therefore 12 to 16px, not the container width that Tailwind gives the name: the skill says so. In `dist/`, 624 class names that did nothing go away and 144 come in. `utilities/sizing.css` drops from 529 kB to 173 kB, and `volver.css` from 1425 kB to 1071 kB, 120.3 kB to 113.2 kB gzipped.
* The `duration-` and `delay-` classes wrote their value, `.duration-300` as `transition-duration: .3s`, while the duration tokens are `0s` unless the user allows motion, so a transition set by a class kept moving for a user who had asked for less motion. The classes now read the tokens, with the value as a fallback: `var(--duration-300, .3s)`. A delay reads the same duration tokens and drops to 0s with the preference, as the transitions of the components do. The shadow, opacity, blur, brightness, contrast, saturate, backdrop blur, `transition-property-` and `ease-` classes read their tokens the same way, so a token set on the page, `--shadow-md` or `--opacity-50`, reaches the classes as it already reached the components.

A shadow token is written with `var(--color-shadow)`, which resolves where the token is declared, on the root and on every `.theme`. A `--color-shadow` set on another element no longer recolours the shadow classes inside it, which is how the shadows of the components already behaved.

Reading the token also renames the default backdrop blur, because the generator gives the bare class name to the key that matches the token. `.backdrop-blur-blur` is now `.backdrop-blur`, like `.blur`, and the old name is kept on the same rule.

In `dist/` the declarations of these classes change and no other rule moves. Every token they read is declared in `props.css`.
* The alias that styles a plain `div` as the list of `vv-dropdown` matched `[role="list-box"]`, which is not an ARIA role, so a `<div role="listbox">` without the `vv-dropdown__list` class was left unstyled. It reads `[role="listbox"]` now. `VvDropdown` of `@volverjs/ui-vue` puts the class on its list and renders as before. In `dist/` the 12 rules of the list change their selector and none of their declarations.
* `scheme`, the `color-scheme` of the theme, is in the map of the colors, and came out as `.bg-scheme`, `.text-scheme`, `.border-scheme` and `.decoration-scheme`, which wrote `light` as a color. The color utilities leave it out, as they leave out `tint`, and `--color-scheme` stays declared. In `dist/` the four rules go away.

* A nested map in `spread-map-into-utilities` was walked with every breakpoint under the prefix of the current one: at the first, of 0, it emitted the prefixed classes with no media query, and it emitted every rule of the nested map once per breakpoint. The recursion now takes the current breakpoint alone. No map of the library reaches the branch since the sizing classes stopped walking the dynamic spacing map, above, so `dist/` does not move; before that the sizing classes gave 630 classes such as `.xs:w-sm` that applied at every width and 4,410 identical copies of the others, and an application that spreads a nested map of its own with breakpoints still did.
* A `transition` declared by a state or a modifier of a block that has Vue transitions shares its name with the `transition` map of the block, which the generator took for a declaration: it reassigned a `--vv-*-transition` that no rule reads, and the transition never applied. A map in the block is no longer taken for a declaration. No component reached the branch before the fade in of `vv-dialog`.
* The sitemap of the documentation listed every page without the `/style` of the site, `https://volverjs.github.io/components/accordion` instead of `https://volverjs.github.io/style/components/accordion`: the routes are absolute, so they replaced the path of the hostname. The sitemap is now generated with `basePath: 'style'`, and `robots.txt` still points to `/style/sitemap.xml`.
* The color swatch of the readable check, on the colors page of the documentation, sat 16px below the fields next to it and was 8px shorter than them. It was aligned to the end of the row, which includes the `--input-gap` margin each field keeps under its wrapper, and it was 44px tall where a field is `--input-min-height`, 52px. The swatch now takes that margin and that height from the same tokens, so it lines up with the wrapper of the fields and stays lined up when a theme changes them.

`design-tokens.json` and the `exports` map do not change.

## [0.1.30] - 2026-10-07

### Added
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,5 +192,7 @@ context.$vv-button: map.deep-merge(
## Documentation
To learn more about `@volverjs/style`, check [its documentation](https://volverjs.github.io/style).

For coding agents, the documentation publishes [`llms.txt`](https://volverjs.github.io/style/llms.txt), an index of its pages, and [`llms-full.txt`](https://volverjs.github.io/style/llms-full.txt), the whole reference of the [agent skill](skills/volverjs-style) in one file. The skill installs with `npx skills add volverjs/style`.

## License
[MIT](http://opensource.org/licenses/MIT)
4 changes: 4 additions & 0 deletions auto-imports.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ declare global {
const EffectScope: typeof import('vue').EffectScope
const asyncComputed: typeof import('@vueuse/core').asyncComputed
const autoResetRef: typeof import('@vueuse/core').autoResetRef
const componentSections: typeof import('./docs/settings/menuSections.js').componentSections
const computed: typeof import('vue').computed
const computedAsync: typeof import('@vueuse/core').computedAsync
const computedEager: typeof import('@vueuse/core').computedEager
Expand Down Expand Up @@ -295,6 +296,7 @@ declare global {
const useWindowScroll: typeof import('@vueuse/core').useWindowScroll
const useWindowSize: typeof import('@vueuse/core').useWindowSize
const utilityColors: typeof import('./docs/utils/utilityColors.js').utilityColors
const utilityGroups: typeof import('./docs/settings/menuSections.js').utilityGroups
const watch: typeof import('vue').watch
const watchArray: typeof import('@vueuse/core').watchArray
const watchAtMost: typeof import('@vueuse/core').watchAtMost
Expand Down Expand Up @@ -327,6 +329,7 @@ declare module 'vue' {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly componentSections: UnwrapRef<typeof import('./docs/settings/menuSections.js')['componentSections']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
Expand Down Expand Up @@ -608,6 +611,7 @@ declare module 'vue' {
readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
readonly utilityColors: UnwrapRef<typeof import('./docs/utils/utilityColors.js')['utilityColors']>
readonly utilityGroups: UnwrapRef<typeof import('./docs/settings/menuSections.js')['utilityGroups']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
Expand Down
22 changes: 13 additions & 9 deletions docs/components/PresetOutlinedFields.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,25 @@
@use '@/layers';

// The keys of `$after` whose value is not the one in `$before`, nested
// maps compared key by key
// maps compared key by key. Written without `@else`: stylelint, which
// SonarCloud runs on the styles of a component, validates `@else` as the
// at-rule of CSS Conditional 5, whose syntax refers to an undefined
// `<boolean-condition>`, and the whole file fails to parse.
@function -changed($before, $after) {
$result: ();

@each $key, $value in $after {
$old: map.get($before, $key);
$change: $value;
$changed: $value != $old;

@if meta.type-of($value) == 'map' and meta.type-of($old) == 'map' {
$nested: -changed($old, $value);
$change: -changed($old, $value);
$changed: list.length($change) > 0;
}

@if list.length($nested) > 0 {
$result: map.set($result, $key, $nested);
}
} @else if $value != $old {
$result: map.set($result, $key, $value);
@if $changed {
$result: map.set($result, $key, $change);
}
}

Expand All @@ -51,8 +55,8 @@
.docs-preset-outlined-fields {
// The `--input-*` tokens the preset changes, declared on this element
// instead of the root. The ones it leaves alone, such as the colours
// the dark theme sets for `valid` and `invalid`, keep coming from the
// root, as they do in a build with the preset.
// of `valid` and `invalid`, keep coming from the root, as they do in a
// build with the preset.
@include ctx.layer(props, ctx.$use-css-layers, ctx.$layer-prefix) {
@include ctx.spread-map-into-props(
$map: -changed($-default-input, ctx.$input),
Expand Down
9 changes: 8 additions & 1 deletion docs/components/ReadableCheck.vue
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,18 @@
class="font-mono" />
</div>
</div>
<!-- the fields keep --input-gap under their wrapper: the swatch
takes the same margin and the height of the wrapper to line up -->
<input
v-model="value"
type="color"
aria-label="Pick a color"
class="self-end w-44 h-44 p-0 border-0 rounded cursor-pointer" />
class="self-end p-0 border-0 rounded cursor-pointer"
style="
inline-size: var(--input-min-height);
block-size: var(--input-min-height);
margin-block-end: var(--input-gap);
" />
</div>
<div class="flex flex-wrap gap-xs">
<button
Expand Down
10 changes: 1 addition & 9 deletions docs/components/TableUtility.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,6 @@
type: Array,
default: () => [],
},
/**
* The classes declare the value itself rather than reading the custom
* property: show the value even when the custom property column is on.
*/
literal: {
type: Boolean,
default: false,
},
})

const items = computed(() => {
Expand Down Expand Up @@ -121,7 +113,7 @@
<slot name="value" v-bind="{ value, key }">
{{
`${attribute || property}: ${
customProperty !== undefined && !literal
customProperty !== undefined
? `var(${getCustomProperty(key)})`
: value
};`
Expand Down
2 changes: 2 additions & 0 deletions docs/contents/components/accordion/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ uiVue: true

An accordion is a native `<details class="vv-accordion">` with a `summary` and a `vv-accordion__content`: the browser opens and closes it and tells assistive technology its state, so no `aria-expanded` is needed. A `vv-accordion-group` stacks several of them.

The content opens and closes with a transition of its height where the browser can animate a height to `auto`, through `::details-content` and `interpolate-size` (Chrome); elsewhere, and under reduced motion, it opens at once.

<code-editor resource-folder="accordion" resource-name="standard" class="mb-lg"></code-editor>

### Disabled
Expand Down
2 changes: 2 additions & 0 deletions docs/contents/components/dialog/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ uiVue: true

A `<dialog class="vv-dialog">` is the backdrop, and the panel is the `article.vv-dialog__wrapper` inside it, which the dialog needs: a `__header` with the title and a `__close` button, a `__content` and a `__footer`. Name the dialog with `aria-labelledby` pointing to its title.

A dialog opened with `showModal()` fades in from the `@starting-style` of the block and closes at once: a fade out would keep a dialog that `@volverjs/ui-vue` hides on the page, invisible and in the way of the pointer, until the fade ends. With Vue, the transitions below animate both ways.

`vv-dialog--small` and `vv-dialog--fullscreen` change the size. The drawers dock the panel to a side of the viewport: `vv-dialog--drawer` at the inline end, `--drawer-start`, `--drawer-top` and `--drawer-bottom`. The `fade-*`, `scale` and `slide-*` classes are the enter and leave classes of a Vue `<Transition>` of the same name: pair each drawer with the slide from its side, `slide-inline-end` for `drawer`, `slide-inline-start` for `drawer-start`, `slide-block-start` for `drawer-top` and `slide-block-end` for `drawer-bottom`.

<code-editor resource-folder="dialog" resource-name="example"></code-editor>
2 changes: 2 additions & 0 deletions docs/contents/components/popover/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ isNew: true

The element carries `popover`, and a button with `popovertarget` opens it: the browser moves it to the top layer and shows it while it is open. The block puts back the fixed position the reset of the library unsets, at the top of the stacking order, with a `__title` and a `__footer` for actions. Placing it next to its trigger is left to the script that opens it, or to anchor positioning where the browser has it, which set its inset. Without either the panel opens at the top left corner of the viewport; `vv-popover--center` centers it, as the example does. A panel that opens on hover is the same block, with the delay in the script; menus and lists of options stay a [Dropdown](/style/components/dropdown).

A panel with `popover` fades in and out: `@starting-style` gives the fade in its first frame, and `transition-behavior: allow-discrete` keeps the panel on the page until the fade out ends. Where the browser lacks one of them it skips that half (Safari 17 fades the panel in and hides it at once), and under reduced motion it shows and hides at once. A panel shown without `popover` does not fade.

<code-editor resource-folder="popover" resource-name="default"></code-editor>
Loading
Loading