# @volverjs/style A CSS/SCSS design system: BEM components (`.vv-button`, `.vv-button__label`, `.vv-button--primary`), Tailwind-like utilities (`p-md`, `flex`, `grid-cols-3`), design tokens as CSS custom properties, and an SCSS layer that generates all of it from maps. Every selector is wrapped in `:where()`, so it has zero specificity and any CSS you write wins without `!important`. The vocabulary is close enough to Tailwind that guessing feels safe. It is not: a class that does not exist fails silently, and about a third of the classes an agent tends to guess (`display-flex`, `md:p-16`, `z-10`, `rounded-2xl`, `text-uppercase`, `hidden`) are not in this library. Work from the reference files, not from memory. ## Workflow 1. **Locate the installed library.** In a consuming project it is `node_modules/@volverjs/style/` (the package ships `src/`, `dist/` and `design-tokens.json`). Inside the library repo itself use `./src` and `./dist` (`pnpm build` regenerates `dist`). 2. **Read the reference for what you are about to write** (all in `references/`): - [utilities.md](references/utilities.md): every utility class with its exact value set and whether it has responsive variants. - [components.md](references/components.md): canonical markup, elements, modifiers and states for all 55 components, plus how states are expressed. - [tokens.md](references/tokens.md): custom properties, CSS-only overrides, dark theme. - [scss.md](references/scss.md): import paths, context configuration, extending and creating components, mixins, cascade layers. 3. **Verify anything not in the references** against the compiled CSS before using it: ```bash # does a utility exist? (responsive form: escape the colon) grep -c -F '.gap-md' node_modules/@volverjs/style/dist/utilities.css grep -c -F '.md\:grid-cols-3' node_modules/@volverjs/style/dist/utilities.css # which modifiers does a component have? grep -oE '\.vv-alert--[a-z-]+' node_modules/@volverjs/style/dist/components/vv-alert.css | sort -u # the full map of a component (elements, modifiers, states, aliases) cat node_modules/@volverjs/style/src/settings/components/_vv-alert.scss ``` 4. **Prefer the library's idiom over custom CSS**: a token override or a modifier from the map beats a new rule, and stays coherent with the dark theme. ## Where it differs from Tailwind These are the mistakes that recur. Each row is verified against the compiled CSS. | Habit | In @volverjs/style | |---|---| | `display-flex`, `hidden` | Bare names: `flex` `grid` `block` `inline-flex` `none` (there is no `hidden`) | | `relative`, `absolute` | Same bare names, fine | | `md:p-16`, `lg:gap-8` | Spacing and gap have **no responsive variants**. Use the dynamic tokens `xs sm md lg xl` (`p-md`, `gap-lg`), which grow with the viewport by themselves | | `xxs:` prefix, `2xl:` | Prefixes are `xs: sm: md: lg: xl: xxl: xxxl:` (360, 576, 992, 1024, 1280, 1440, 1536px) and only display, position, visibility, flex/grid, sizing, text size/align, border width/style, aspect and columns accept them | | `@md:flex-row` with no container | Container prefixes `@xs:` `@sm:` `@md:` `@lg:` `@xl:` `@xxl:` `@xxxl:` (20, 24, 28, 32, 36, 42, 48rem) read the nearest ancestor with the `@container` class and match nothing without one. Only display, flex direction and wrap, justify/align content and items, `grid-cols-`, `col-` and text align have them | | `hover:bg-…`, `dark:text-…`, `focus:` | No state or theme variants at all. Use component states, tokens, or your own CSS | | `max-w-md`, `max-w-2xl` | `max-w-screen-md` … `max-w-screen-xxxl`, `max-w-prose`, or `container`. `max-w-md` exists, but it is the dynamic spacing step (12 to 16px) | | `z-10` … `z-50` | Semantic: `z-sticky` `z-fixed` `z-dropdown` `z-modal-backdrop` `z-modal` `z-popover` `z-tooltip` `z-toast` (plus `z-1`, `z-auto`) | | `aspect-video` | `aspect-wide` (16/9); also `square` `photo` `tv` `ultrawide` | | `rounded-2xl`, `rounded-3xl` | `rounded-xxl`, `rounded-xxxl` (shadows do use `shadow-2xl`) | | `text-xl`, `text-4xl`, `text-40` | Dynamic `text-xs`…`text-xl` (responsive by design) or static `text-12 14 16 18 20 22 24 26 28 30 32 34 36 48 60 72 96 128`. No 40, 44, 56, 64, 80 | | `font-italic`, `text-uppercase`, `tracking-loose` | Bare `italic` `non-italic` `uppercase` `lowercase` `capitalize`; `tracking-tighter/tight/normal/wide/wider/widest` | | `text-blue-500`, `bg-gray-100` | Semantic palette: `text-word` `text-word-2` `bg-surface-1` `border-surface-3` `bg-surface-brand`, and for text in a color a role, `text-brand-readable` `text-danger-readable`, never a shade | | `space-y-4`, `divide-y` | Not available: use `flex flex-col gap-md` | | `w-1/2` | Exists, written literally: `w-1/2`, `col-span-6` | | `border-none` to remove a border | `border-0` (`border-none` is `border-style: none`) | | `p-30`, `m-11` | The scale has no 11, 15, 30, 60, 72: see the list in utilities.md | Color semantics: `word` is text (`word-1`…`word-5` progressively fainter), `surface` is background (`surface-1`…`surface-5` progressively deeper), `surface-{brand,success,…}` are tinted backgrounds for badges and callouts, `alpha-*` are translucent overlays. Every palette color has `-lighten-1..5` and `-darken-1..5`, which are fills, plus roles that stay legible whatever color is set at runtime, in both themes: | What you are painting | Token, for brand (same for accent, success, danger, info, warning) | |---|---| | Text on the page, down to `surface-2` | `text-brand-readable`; a step further, a pressed link: `text-brand-readable-strong` | | Text on a tinted surface | `bg-surface-brand text-surface-brand-readable` | | Text on a fill | `bg-brand text-brand-contrast`, `bg-brand-darken-1 text-brand-darken-1-contrast` | | A bar, a focus ring, a border that shows a state | `bg-brand-graphic`, `border-brand-graphic` (3:1, not text) | A shade written as text (`text-brand`, `text-danger-darken-1`) reads only by chance: with a light tenant brand, or in the dark theme, it can fall to 1.5:1. ## Layout recipes (verified) ```html
…
…
…
…
…
…

Title

Secondary copy

Long text ``` ## Components Naming: block `.vv-{name}`, element `.vv-{name}__{el}`, modifier `.vv-{name}--{mod}`. Available: `vv-button` `vv-button-group` `vv-input-text` `vv-textarea` `vv-select` `vv-checkbox` `vv-radio` `vv-checkbox-group` `vv-radio-group` `vv-input-file` `vv-input-range` `vv-card` `vv-dialog` `vv-alert` `vv-alert-group` `vv-dropdown` `vv-dropdown-action` `vv-dropdown-option` `vv-dropdown-optgroup` `vv-nav` `vv-tab` `vv-accordion` `vv-accordion-group` `vv-table` `vv-badge` `vv-avatar` `vv-avatar-group` `vv-tooltip` `vv-progress` `vv-skeleton` `vv-spinner` `vv-breadcrumb` `vv-text` `vv-empty` `vv-separator` `vv-item` `vv-item-group` `vv-field` `vv-prose` `vv-pagination` `vv-message` `vv-bubble` `vv-bubble-group` `vv-message-scroller` `vv-attachment` `vv-attachment-group` `vv-marker` `vv-questionnaire` `vv-kbd` `vv-kbd-group` `vv-popover` `vv-command` `vv-input-otp` `vv-sidebar` `vv-calendar`. Three facts shape the markup; components.md has the full structure of each component. **Semantic children replace element classes.** Most elements have an alias, so the natural tag is styled with no class: `.vv-input-text > label`, `.vv-button > svg`, `.vv-card > header`, `.vv-dialog > article`, `.vv-accordion > summary`. Elements without an alias (`__wrapper`, `__content`, `__close`, `__hint` on a ``) need the class. **States come from the DOM, not from classes.** Each state also has a natural selector: `[disabled]` or `[aria-disabled="true"]`, `[readonly]`, `[open]`, `[aria-pressed="true"]`, `[aria-selected="true"]`, `:hover`, `:focus-visible`. Write `
Enter a valid address
Title
…
…
Title
…
…
``` Button modifiers: `primary` `secondary` `danger` `ghost` `link` `action` `action-quiet` `static-light` `static-dark` `icon-only` `rounded` `block` `reverse` `column` `full-bleed`. The default button is the brand accent; there is no `accent`, `loading` or size modifier. Traps: `vv-dialog` needs the `
` panel (the `` itself is the backdrop) and has no `__title`; `vv-alert` has no `__description`/`__action`, icon and title live in `__header`; `vv-card__picture` goes on the ``; `vv-alert-group` wraps its alerts in `
`; the checkbox switch is `vv-checkbox--switch`; `vv-input-range` needs `style="--input-range-progress: 45%"` on the block or the track renders empty. ## Tokens and theming Tokens live on `:where(:host, :root, .theme)`, so `:root { --color-brand: #45cb85 }` recolors the whole palette (shades derive with relative color syntax; there are no `--color-brand-hue/saturation/lightness` channels). Component properties are prefixed with the block: `--vv-button-background`, `--vv-button-state-hover-background`, `--vv-button-modifier-primary-background`. Fields share `--input-*`. ```css :root, :host, .theme { --color-brand: #45cb85; --color-tint: #45cb85; --rounded: 0.5rem; } ``` `--rounded` is the base of the radius scale: `--rounded-sm` … `--rounded-xxxl` are multiples of it, so it rescales every corner (`none` and `full` stay). `--color-tint` gives the brand hue to gray, word and surface too; leave it out to keep the neutrals. The readable, contrast and graphic roles follow the new brand by themselves. A new button variant goes through the SCSS map (see scss.md) or, in plain CSS, names the hover state itself, since a class beats the zero specificity of the library's state rules: the recipe is in tokens.md. Dark theme is a separate stylesheet (`@import '@volverjs/style/themes/dark'` or `@use '@volverjs/style/scss/themes/dark'`) loaded after the base. With it loaded the page follows `prefers-color-scheme`; force with `class="theme theme--dark"` or `class="theme theme--light"` on `` or on any subtree. Both classes are required; there is no `data-theme`. Keep custom colors as `var(--color-…)` so they follow the theme. ## SCSS ```scss @use '@volverjs/style/scss/context' with ( $color-brand: #45cb85, $font-family-sans: "'Inter', sans-serif", // one quoted string, or it will not compile $use-css-layers: true ); @use '@volverjs/style/scss'; @use '@volverjs/style/scss/themes/dark'; ``` Extend a component by deep-merging its map before the library is emitted (`ctx.$vv-button: map.deep-merge(ctx.$vv-button, (modifier: (success: (…))))`), create one with `spread-map-into-bem`, and query breakpoints with `@include ctx.bp-up(md)`, the nearest `@container` with `@include ctx.cq-up(md)`. When cherry-picking, alias modules that share a basename (`props/spacing` and `utilities/spacing` collide) and take `scss/props` whole with any component. Layers are emitted as `volver.reset`, `volver.utilities`, … Details and signatures in scss.md. ## With @volverjs/ui-vue Vue components render these classes; `modifiers="primary"` becomes `.vv-button--primary`, so valid modifier names are the ones in components.md. Utilities go on wrappers and slots as usual (``). Import `@volverjs/style` once in the entry file and the dark theme next to it; toggle `theme theme--dark` on `` (the library docs use `useDark({ attribute: 'class', valueDark: 'theme theme--dark', valueLight: 'theme theme--light' })`). ## Before you finish - Every class you wrote is in the references or was confirmed with grep. - Responsive prefixes only on groups that support them; spacing uses dynamic tokens. A `@` container prefix also needs an ancestor with the `@container` class. - Disabled comes from the attribute, and readonly too where HTML has one; elsewhere readonly is the modifier. Hints are linked with `aria-describedby`, labels have `for`. - Dialogs have the `__wrapper` article; range inputs have `--input-range-progress`. - Colors are tokens, not hex, so the dark theme keeps working. - Text in a color is a `-readable` role, text on a fill its `-contrast`, a bar or a ring its `-graphic`: never a shade, which stops reading with a light brand or in the dark. --- # Utility classes Every class below was verified against `src/settings/*.scss`, `src/utilities/*.scss` and the compiled `dist/utilities/*.css`. If a class is not in this file, assume it does not exist and check `node_modules/@volverjs/style/dist/utilities/.css` before using it. ## How names are built - `{prefix}-{value}` for most groups: `p-16`, `gap-md`, `text-brand`, `w-1/2`. - **Bare value names** (no prefix) for display, position, visibility, font style, text transform, text decoration line, font smoothing and font-variant-numeric: `flex`, `grid`, `none`, `relative`, `invisible`, `italic`, `uppercase`, `underline`. - Responsive variant: `{breakpoint}:{class}`, mobile-first (`min-width`), **only for the groups marked "Responsive: yes" below**. Prefixes: `xs:` `sm:` `md:` `lg:` `xl:` `xxl:` `xxxl:`. There is no `xxs:` prefix (xxs is 0px, the unprefixed class). | Breakpoint | xs | sm | md | lg | xl | xxl | xxxl | |---|---|---|---|---|---|---|---| | min-width | 360px | 576px | 992px | 1024px | 1280px | 1440px | 1536px | - Container variant: `@{step}:{class}` (`@md:flex-row`), the width of the nearest ancestor with the `@container` class (`container-type: inline-size`); with none it never applies. Only for display, `flex-` direction and wrap, `justify-` `content-` `justify-items-` `items-`, `grid-cols-`, `col-` (span, auto, full) and `text-` align. Emitted after the responsive classes of the same utility, so it wins when both apply. | Container step | @xs | @sm | @md | @lg | @xl | @xxl | @xxxl | |---|---|---|---|---|---|---|---| | min-width | 20rem | 24rem | 28rem | 32rem | 36rem | 42rem | 48rem | ## Scales shared by several groups **Static spacing scale** (used by margin, padding, gap, inset, sizing): ```text 0 px 1 2 3 4 5 6 7 8 9 10 12 14 16 18 20 22 24 26 28 32 34 36 38 40 44 48 50 52 56 64 80 96 100 112 128 144 150 160 176 192 208 224 240 256 288 300 320 384 ``` There is no 11, 15, 30, 60 or 72 step. `px` is 1px. **Dynamic spacing scale**: `xs` `sm` `md` `lg` `xl`. Each one resolves to a different static step per viewport, so the same class breathes on larger screens: | token | < 576px | ≥ 576px | ≥ 1024px | |---|---|---|---| | xs | 6 | 8 | 10 | | sm | 10 | 12 | 14 | | md | 12 | 14 | 16 | | lg | 16 | 22 | 36 | | xl | 24 | 36 | 48 | Prefer the dynamic tokens for layout rhythm (`p-md`, `gap-lg`, `mb-xl`) and static numbers only for pixel-exact needs. Spacing utilities have **no** responsive variants, so `md:p-16` does nothing: the dynamic tokens are how spacing adapts. **Color names** (used by `text-`, `bg-`, `border-`, `decoration-`): ```text brand accent success danger info warning gray each also -lighten-1..5 and -darken-1..5 brand-contrast accent-contrast success-contrast white or black, the text that reads on that fill, danger-contrast info-contrast warning-contrast also -darken-1..5-contrast, text- only (no gray-contrast) brand-readable brand-readable-strong the color as text on the surface, text- only surface-brand-readable surface-success-readable ... the color as text on its tinted surface, text- only brand-graphic accent-graphic ... the color as a bar, ring or state border, 3:1 word word-1..word-5 text colors, from strongest to faintest surface surface-1..surface-5 backgrounds, from page to deepest inset surface-brand surface-accent surface-success surface-danger surface-info surface-warning alpha alpha-1..alpha-5 translucent overlays white black transparent shadow backdrop ``` ## Spacing (not responsive) | Class | Values | Notes | |---|---|---| | `m-` `mx-` `my-` `mt-` `mr-` `mb-` `ml-` | spacing scale + dynamic | margin | | `p-` `px-` `py-` `pt-` `pr-` `pb-` `pl-` | spacing scale + dynamic | padding | | `n-` `nx-` `ny-` `nt-` `nr-` `nb-` `nl-` | spacing scale + dynamic | **negative** margin (`n-16` = `margin: -16`) | | `m-auto` `mx-auto` `my-auto` `mt-auto` `mr-auto` `mb-auto` `ml-auto` | | margin only, no `p-auto` | ## Layout | Class | Values | Responsive | |---|---|---| | display (bare) | `block` `inline-block` `inline` `flex` `inline-flex` `grid` `inline-grid` `none` | yes | | display (bare) | `table` `inline-table` `table-caption` `table-cell` `table-column` `table-column-group` `table-footer-group` `table-header-group` `table-row-group` `table-row` `flow-root` `contents` `list-item` | no | | position (bare) | `static` `fixed` `absolute` `relative` `sticky` | yes | | visibility (bare) | `visible` `invisible` | yes | | `inset-` `inset-x-` `inset-y-` `top-` `right-` `bottom-` `left-` | spacing scale + dynamic + `1/2` `1/3` `2/3` `1/4` `2/4` `3/4` `full` `auto` | no | | `z-` | `1` `sticky` `fixed` `dropdown` `modal-backdrop` `modal` `confirm-backdrop` `confirm` `popover` `tooltip` `toast` `notification-alert` `auto` | no | | `overflow-` `overflow-x-` `overflow-y-` | `visible` `hidden` `scroll` `auto` | no | | `scrollbar-` | `thin` (thin, `word-5` thumb on a transparent track) `none` (hidden, still scrolls) | no | | `container` | | width 100%, max-width follows the current breakpoint, the whole width below `xs` (360px); no size suffixes | | `@container` | | `container-type: inline-size`, the box the `@` variants measure; it stops taking its width from its content, so not on an `inline-flex` or a flex item with no basis; no prefixes | | `aspect-` | `auto` `square` `photo` (3/2) `tv` (4/3) `wide` (16/9) `ultrawide` (21/9) | yes | | `object-` (fit) | `fill` `contain` `cover` `none` `scale-down` | no | | `object-` (position) | `top` `bottom` `left` `right` `center` `left-top` `left-bottom` `right-top` `right-bottom` | no | | `columns-` | `1`..`12` | yes | | `break-after-` `break-before-` | `auto` `avoid` `all` `avoid-page` `page` `left` `right` `column` | no | | `break-inside-` | `auto` `avoid` `avoid-page` `avoid-column` | no | The z-index scale is semantic and stacks in the order listed (1, 1010, 1020, 1025, 1030, 1040, 1050, 1060, 1070, 1080, 1090, 1100). There is no `z-10`/`z-50`. `display: none` is the class `none`; there is no `hidden` class. ## Flexbox | Class | Values | Responsive | |---|---|---| | `flex-` (direction) | `row` `col` `row-reverse` `col-reverse` | yes | | `flex-` (wrap) | `wrap` `nowrap` `wrap-reverse` | yes | | `flex-` (size) | `1` `auto` `initial` `none` | yes | | `grow` `grow-0` `shrink` `shrink-0` | | no | | `order-` | `1`..`12` `first` `last` `none` | yes | | `justify-` | `start` `end` `center` `between` `around` `evenly` | yes | | `justify-items-` | `start` `end` `center` `baseline` `stretch` | yes | | `justify-self-` | `auto` `start` `end` `center` `baseline` `stretch` | yes | | `items-` | `start` `end` `center` `baseline` `stretch` | yes | | `self-` | `auto` `start` `end` `center` `baseline` `stretch` | yes | | `content-` (align-content) | `start` `end` `center` `between` `around` `evenly` | yes | | `gap-` `gap-x-` `gap-y-` | spacing scale + dynamic | **no** | ## Grid All grid utilities are responsive. | Class | Values | |---|---| | `grid-cols-` | `1`..`12` `none` | | `grid-rows-` | `1`..`6` `none` | | `col-span-` | `1`..`12` `full`; plus `col-auto` | | `row-span-` | `1`..`6` `full`; plus `row-auto` | | `col-start-` `col-end-` | `1`..`13` `auto` | | `row-start-` `row-end-` | `1`..`7` `auto` | | `grid-flow-` | `row` `col` `dense` `row-dense` `col-dense` | | `auto-cols-` `auto-rows-` | `auto` `min` `max` `fr` | `gap-*` is shared with flexbox and is not responsive. ## Sizing (all responsive) | Class | Values | |---|---| | `w-` | spacing scale, `auto` `full` `screen` `min` `max` `fit`, fractions `1/2` `1/3` `2/3` `1/4` `2/4` `3/4` `1/5`..`4/5` `1/6`..`5/6` `1/12`..`11/12` | | `h-` | spacing scale, `auto` `full` `screen` `min` `max` `fit`, fractions `1/2`..`5/6` (no twelfths) | | `max-w-` | spacing scale, `0` `none` `full` `min` `max` `fit` `prose` (65ch), `screen-xs` `screen-sm` `screen-md` `screen-lg` `screen-xl` `screen-xxl` `screen-xxxl` | | `max-h-` | spacing scale, `full` `screen` `min` `max` `fit` | | `min-w-` | spacing scale, `0` `full` `min` `max` `fit` | | `min-h-` | spacing scale, `0` `full` `screen` `min` `max` `fit` | `screen` uses `100dvw`/`100dvh`. The spacing scale includes the dynamic steps (`w-md`, `h-lg`), which grow with the viewport like the spacing does. So the Tailwind container idiom `max-w-md` is 12 to 16px here, not a container width: use `max-w-screen-md` or `container`. Fractions are written literally in the markup (`class="w-1/2"`). ## Typography | Class | Values | Responsive | |---|---|---| | `text-` (size, static) | `smaller` `larger` `6` `8` `10` `12` `14` `16` `18` `20` `22` `24` `26` `28` `30` `32` `34` `36` `48` `60` `72` `96` `128` | yes | | `text-` (size, dynamic) | `xs` `sm` `md` `lg` `xl` | yes | | `text-` (align) | `left` `center` `right` `justify` `start` `end` | yes | | `text-` (color) | color names | no | | `text-ellipsis` `text-clip` `text-balance` | | no | | `font-` (family) | `sans` `serif` `mono` | no | | `font-` (weight) | `thin` `extralight` `light` `normal` `medium` `semibold` `bold` `extrabold` `black` | no | | font style (bare) | `italic` `non-italic` | no | | `leading-` | `none` `tight` `snug` `normal` `relaxed` `loose` | no | | `tracking-` | `tighter` `tight` `normal` `wide` `wider` `widest` | no | | text transform (bare) | `uppercase` `lowercase` `capitalize` `normal-case` | no | | decoration line (bare) | `underline` `overline` `line-through` `no-underline` | no | | `decoration-` | color names; style `solid` `double` `dotted` `dashed` `wavy`; thickness `auto` `from-font` `0` `1` `2` `4` `8` | no | | `underline-offset-` | `auto` `from-font` `0` `1` `2` `4` `8` | no | | `whitespace-` | `normal` `nowrap` `pre` `pre-line` `pre-wrap` | no | | word break | `break-normal` `break-words` `break-all` `truncate` | no | | `align-` (vertical-align) | `baseline` `top` `middle` `bottom` `text-top` `text-bottom` `sub` `super` | no | | `list-` | `none` `disc` `decimal` `lower-latin` `upper-latin` `lower-roman` `upper-roman` `inside` `outside` | no | | font smoothing (bare) | `antialiased` `subpixel-antialiased` | no | | numeric variants (bare) | `normal-nums` `ordinal` `slashed-zero` `lining-nums` `oldstyle-nums` `proportional-nums` `tabular-nums` `diagonal-fractions` `stacked-fractions` | no | Dynamic text sizes resolve per viewport like dynamic spacing: | token | < 576px | ≥ 576px | ≥ 1024px | |---|---|---|---| | xs | 10 | 12 | 14 | | sm | 12 | 14 | 16 | | md | 14 | 16 | 18 | | lg | 16 | 18 | 20 | | xl | 18 | 20 | 22 | There is no `text-40`, `text-44`, `text-56`, `text-64` or `text-80`; the scale jumps 36 → 48 → 60 → 72 → 96 → 128. ## Colors and backgrounds (not responsive) | Class | Values | |---|---| | `text-` `bg-` `border-` `decoration-` | color names (see shared scales) | | `bg-` (size) | `auto` `cover` `contain` | | `bg-` (position) | `top` `bottom` `left` `right` `center` `left-top` `left-bottom` `right-top` `right-bottom` | | `bg-` (repeat) | `repeat` `no-repeat` `repeat-x` `repeat-y` `repeat-round` `repeat-space` | | `bg-` (attachment) | `fixed` `local` `scroll` | | `bg-clip-` | `border` `padding` `content` `text` | | `bg-origin-` | `border` `padding` `content` | | `bg-gradient-` | `1`..`30`, `brand` `accent` `success` `danger` `info` `warning` `gray` | | `bg-noise-` | `1`..`5` | | `bg-` (patterns) | `grid` `chessboard` `check` `chevron` `close` `minus` `none` | `bg-` is overloaded: `bg-center`, `bg-cover`, `bg-fixed`, `bg-none` are not colors. Text on a filled color takes the `-contrast` of that color, not `text-white`: `` stays readable when the brand turns light. On a darker shade take the contrast of that shade: `bg-warning-darken-5 text-warning-darken-5-contrast`. The contrast colors are `text-` classes only. There is no `text-tint`: `--color-tint` has no utilities. Text written in a color takes a readable role, not a shade: `text-brand-readable` on the page, `text-brand-readable-strong` for a stronger one, and on a tinted surface the role of that surface, `
`. A role is the color itself while it reads and moves only when it would not reach 4.6:1, darker in the light theme and brighter in the dark one. They are `text-` classes only: there is no `bg-brand-readable`, and no `text-brand-darken-1-readable`. ## Borders | Class | Values | Responsive | |---|---|---| | `border` `border-0` `border-2` `border-4` `border-8` | width (bare `border` = 1px) | yes | | `border-x-` `border-y-` `border-t-` `border-r-` `border-b-` `border-l-` | `0` `2` `4` `8`; bare side (`border-t`) = 1px | yes | | `border-` (style) | `solid` `dashed` `dotted` `double` `hidden` `none` | yes | | `border-` (color) | color names | no | | `rounded` `rounded-` | `none` `sm` (bare `rounded` = .25rem) `md` `lg` `xl` `xxl` `xxxl` `full` | no | | `rounded-t-` `rounded-b-` `rounded-tl-` `rounded-tr-` `rounded-bl-` `rounded-br-` | same radius names; bare corner (`rounded-tl`) = .25rem | no | `border-none` sets `border-style: none`; to remove a border width use `border-0`. The radius scale is `xxl`/`xxxl`, not `2xl`/`3xl`. ## Effects, filters, transitions (not responsive) | Class | Values | |---|---| | `shadow` `shadow-` | `sm` (bare `shadow` = default) `md` `lg` `xl` `2xl` `inner` `none` | | `opacity-` | `0` `5` `10` … `95` `100` (every multiple of 5) | | `mix-blend-` `bg-blend-` | CSS blend mode names (`multiply` `screen` `overlay` …) | | `blur` `blur-` | `none` `sm` (4px) `md` (12px) `lg` (16px) `xl` (24px) `xxl` (40px) `xxxl` (64px); bare `blur` = 8px | | `backdrop-blur` `backdrop-blur-` | `none` `sm` `md` `lg` `xl` `xxl` `xxxl`; bare `backdrop-blur` = 8px (`backdrop-blur-blur` is kept as an alias) | | `brightness-` | `0` `50` `75` `90` `95` `100` `105` `110` `125` `150` `200` `250` `500` `1000` | | `contrast-` | `0` `50` `75` `100` `125` `150` `200` `300` | | `saturate-` | `0` `50` `100` `150` `200` | | `transition-` | `none` `all` `colors` `opacity` `shadow` `transform` (each sets duration 300 and ease-in-out) | | `transition-property-` | same names, property only | | `duration-` `delay-` | `75` `100` `150` `200` `300` `500` `700` `1000` | | `ease-` | `linear` `in` `out` `in-out` | Transition and duration tokens are `0s` unless the user has `prefers-reduced-motion: no-preference`, so motion switches itself off for users who asked for it, and the `duration-` and `delay-` classes read them. The shadow, opacity, filter, `transition-property-` and `ease-` classes read their tokens too, with the value as a fallback, so a token set on the page reaches them. There are no `animate-*` utilities; animation keyframes exist only as `--animation-*` custom properties. ## Transforms (not responsive) | Class | Values | |---|---| | `scale-` `scale-x-` `scale-y-` | `0` `50` `75` `90` `95` `100` `105` `110` `125` `150` | | `rotate-` | `0` `1` `2` `3` `6` `12` `45` `90` `180` | | `translate-` `translate-x-` `translate-y-` | `1/2` `1/3` `2/3` `1/4` `2/4` `3/4` `full` | | `-translate-` `-translate-x-` `-translate-y-` | same, negative | | `origin-` | `center` `top` `top-right` `top-left` `bottom` `bottom-right` `bottom-left` `right` `left` | ## Interactivity and tables (not responsive) | Class | Values | |---|---| | `cursor-` | `auto` `default` `pointer` `wait` `text` `move` `help` `not-allowed` `none` `grab` `grabbing` `zoom-in` `zoom-out` `progress` `crosshair` `copy` `alias` `cell` `context-menu` `no-drop` `vertical-text` `all-scroll` `col-resize` `row-resize` `n-resize` `e-resize` `s-resize` `w-resize` `ne-resize` `nw-resize` `se-resize` `sw-resize` `ew-resize` `ns-resize` `nesw-resize` `nwse-resize` | | `pointer-events-` | `none` `auto` | | `user-select-` | `none` `text` `all` `auto` | | table (bare) | `border-collapse` `border-separate` `table-auto` `table-fixed` | --- # Components Verified against `src/settings/components/_vv-*.scss`, `dist/components/*.css` and the canonical examples in `docs/contents/components/`. Every class named here exists; markup shown is the documented structure. ## Rules that apply to every component **Naming.** Block `.vv-{name}`, element `.vv-{name}__{element}`, modifier `.vv-{name}--{modifier}`. The `vv` prefix follows `$components-prefix`. **Semantic children replace element classes.** Many elements carry an alias, so the library styles the natural HTML child and the BEM class is optional. Marked with † in the tables below. Examples: `.vv-input-text > label` (no `__label` needed), `.vv-button > svg`, `.vv-card > header`, `.vv-dialog > article`. Elements without an alias (`__wrapper`, `__content`, `__close`, `__hint` on ``... see tables) need the class. Using the alias form keeps markup lean; using the class is never wrong. **States have three equivalent hooks.** For each state the library emits the BEM class (`.vv-button--disabled`), a bare class (`.vv-button.disabled`) and the natural selector (`[disabled]`, `:hover`, `[open]`, `[aria-pressed="true"]`...). Use the natural one in real markup; the classes exist to freeze a look for docs and tests. | state | natural selector | |---|---| | disabled | `[disabled]` or `[aria-disabled="true"]` | | readonly | `[readonly]` | | checked / indeterminate | `:checked` / `:indeterminate` | | open / close | `[open]` / `:not([open])` | | pressed | `[aria-pressed="true"]` | | selected | `[aria-selected="true"]` | | dirty | `:not(:has(*:placeholder-shown))` | | popover | `[popover]` | | hover, active, focus-visible, focus-within, target, empty | the pseudo-class itself | | current (nav, tab) | **class only**: `class="vv-nav__item-label current"` | **Form components share one pattern.** `vv-input-text`, `vv-textarea`, `vv-select`, `vv-input-file`, `vv-input-range`: a block `
` containing, in order, a direct-child `