Labels and states
Name the action directly. Show loading, errors and the result.
01 / Foundations
Highlight one primary action. Use text or an icon alongside color to convey state. Group related elements with spacing and shared backgrounds.
Name the action directly. Show loading, errors and the result.
Use shared sizes and spacing. Similar elements should behave consistently.
Keyboard support, visible focus, field labels and sufficient contrast.
Light theme with Gavia Sans.
Dark theme with a teal accent and Gavia Sans.
Light theme with a blue accent.
Dark theme with status colors.
Light theme with a warm paper background.
Cards and sections / elevation-surface
Menus and panels / elevation-floating
02 / Typography
Size follows the text role. Semantic h1–h6 elements preserve document structure; the class defines the appearance. Newspaper uses its own fonts through the same tokens.
display40px / 48px · 700
Display
Main introductory heading; one per screen.
title28px / 36px · 700
Page title
Page and large dialog title.
heading20px / 28px · 650
Section heading
Page section and independent block.
subheading16px / 24px · 600
Block heading
Card, field group or small dialog.
body14px / 20px · 400
Body text
Descriptions, instructions and form text.
small12px / 18px · 400
Small text
Captions and supporting text.
label13px / 20px · 500
Label
Short action or field label.
code12px / 18px · 400
Code and data
Tokens, identifiers and technical values.
Use body for main content and small + text-muted for supporting text. Keep long lines around 60–75 characters. --wl-text-3 is reserved for decorative details.
03 / Grid and spacing
Small groups: 8 px; form fields: 16 px; cards: 24 px; sections: 48–64 px. Compact density suits screens with a lot of data.
none0px2xs2pxxs4pxsm8pxmd12pxlg16pxxl24px2xl32px3xl48px4xl64pxCards automatically switch to one column. Container: 1240px.
<section class="wl-container">
<div class="wl-stack" data-space="xl">
<h1 class="wl-text-title">Page title</h1>
<div class="wl-grid" data-space="lg">
<article class="wl-surface">Content</article>
</div>
</div>
</section>Import CSS explicitly: styles/primitives.css. Keep a logical reading order on narrow screens, wrap actions and limit horizontal scrolling to tables.
04 / Tokens
Foundation → semantic → component. The catalog, CSS and types are generated from tokens/source.json. Values below use the theme gavia.
.page {
color: var(--wl-text);
background: var(--wl-bg);
gap: var(--wl-space-lg);
}
/* Customize a specific component */
.page-actions { --wl-btn-height: var(--wl-control-height-lg); }Tokens found: 171 / 447
| Token / role | Reference | Value |
|---|---|---|
--wl-type-text-renderingText rendering mode for the selected theme. semantic | var(--wl-font-text-rendering) | geometricPrecision |
--wl-bgPrimary page and content surface. semantic | var(--wl-palette-bg) | #faf9f6 |
--wl-bg-raisedLight surface for cards and local panels above the page background. semantic | var(--wl-palette-bg-raised) | #fefdfb |
--wl-bg-softGrouped blocks and secondary surfaces. semantic | var(--wl-palette-bg-soft) | #eef2f3 |
--wl-bg-hoverInteractive element surface on hover. semantic | var(--wl-palette-bg-hover) | #e5ecef |
--wl-borderSubtle surface dividers. semantic | var(--wl-palette-border) | #d5dfe3 |
--wl-border-2Stronger group border. semantic | var(--wl-palette-border-2) | #b8c7ce |
--wl-textPrimary readable text. semantic | var(--wl-palette-text) | #0f1a23 |
--wl-text-accentAccent text for links, quiet actions and selected items on standard and soft surfaces. semantic | var(--wl-palette-text-accent) | #326179 |
--wl-text-accent-hoverAccent text on hover for links and quiet actions. semantic | var(--wl-palette-accent-hover) | #326179 |
--wl-text-2Secondary readable text and labels. semantic | var(--wl-palette-text-2) | #5b6470 |
--wl-text-3Muted decorative metadata; not for required instructions. semantic | var(--wl-palette-text-3) | #75818c |
--wl-accentAccent for selection, links and primary actions. semantic | var(--wl-palette-accent) | #3c7490 |
--wl-accent-hoverAccent on hover. semantic | var(--wl-palette-accent-hover) | #326179 |
--wl-accent-softRole color accent-soft. semantic | var(--wl-palette-accent-soft) | #dfe8eb |
--wl-accent-borderRole color accent-border. semantic | var(--wl-palette-accent-border) | #b8cbd3 |
--wl-accent-soft-hoverRole color accent-soft-hover. semantic | var(--wl-palette-accent-soft-hover) | #d3dfe4 |
--wl-accent-border-hoverRole color accent-border-hover. semantic | var(--wl-palette-accent-border-hover) | #9eb7c2 |
--wl-on-accentLegacy color on the accent surface. semantic | var(--wl-palette-on-accent) | #ffffff |
--wl-successSuccessful result signal. semantic | var(--wl-palette-success) | #2e9e68 |
--wl-success-softRole color success-soft. semantic | var(--wl-palette-success-soft) | #eaf6f0 |
--wl-success-borderRole color success-border. semantic | var(--wl-palette-success-border) | #cdeadb |
--wl-warnWarning signal. semantic | var(--wl-palette-warn) | #bf8615 |
--wl-warn-softRole color warn-soft. semantic | var(--wl-palette-warn-soft) | #fbf4e2 |
--wl-warn-borderRole color warn-border. semantic | var(--wl-palette-warn-border) | #f0dfae |
--wl-dangerDestructive action or error signal. semantic | var(--wl-palette-danger) | #ab4448 |
--wl-danger-hoverRole color danger-hover. semantic | var(--wl-palette-danger-hover) | #963c3f |
--wl-danger-softRole color danger-soft. semantic | var(--wl-palette-danger-soft) | #f7eeed |
--wl-danger-soft-hoverRole color danger-soft-hover. semantic | var(--wl-palette-danger-soft-hover) | #f3e5e4 |
--wl-danger-borderRole color danger-border. semantic | var(--wl-palette-danger-border) | #e5c6c5 |
--wl-info-textRole color info-text. semantic | var(--wl-palette-info-text) | #1e3f8f |
--wl-ok-textRole color ok-text. semantic | var(--wl-palette-ok-text) | #1c6b47 |
--wl-warn-textRole color warn-text. semantic | var(--wl-palette-warn-text) | #7d5a0e |
--wl-err-textRole color err-text. semantic | var(--wl-palette-err-text) | #ab4448 |
--wl-mask-bgModal backdrop. semantic | var(--wl-palette-mask-bg) | rgba(15, 26, 35, 0.28) |
--wl-avatar-bgRole color avatar-bg. semantic | var(--wl-palette-avatar-bg) | #253540 |
--wl-tooltip-bgTooltip surface. semantic | var(--wl-palette-tooltip-bg) | #0f1a23 |
--wl-tooltip-textTooltip text. semantic | var(--wl-palette-tooltip-text) | #ffffff |
--wl-control-height-mdRole btn-height. semantic | var(--wl-dimension-btn-height) | 40px |
--wl-control-height-smRole btn-height-sm. semantic | var(--wl-dimension-btn-height-sm) | 32px |
--wl-control-height-lgRole btn-height-lg. semantic | var(--wl-dimension-btn-height-lg) | 48px |
--wl-control-height-xsRole btn-height-xs. semantic | var(--wl-dimension-btn-height-xs) | 26px |
--wl-corner-controlRole btn-radius. semantic | var(--wl-radius) | 8px |
--wl-corner-control-smRole btn-radius-sm. semantic | var(--wl-radius-sm) | 6px |
--wl-control-padding-inlineRole btn-pad-x. semantic | var(--wl-dimension-btn-pad-x) | 14px |
--wl-type-control-sizeRole btn-font-size. semantic | var(--wl-dimension-btn-font-size) | 13.5px |
--wl-corner-inputRole input-radius. semantic | var(--wl-dimension-input-radius) | 6px |
--wl-focus-shadowRole focus-ring. semantic | 0 0 0 3px var(--wl-accent-soft) | 0 0 0 3px #dfe8eb |
--wl-focus-invalid-shadowRole focus-ring-danger. semantic | 0 0 0 3px var(--wl-danger-soft) | 0 0 0 3px #f7eeed |
--wl-metric-check-sizeRole check-size. semantic | var(--wl-dimension-check-size) | 18px |
--wl-metric-switch-widthRole switch-width. semantic | var(--wl-dimension-switch-width) | 36px |
--wl-metric-switch-heightRole switch-height. semantic | var(--wl-dimension-switch-height) | 22px |
--wl-metric-tag-heightRole tag-height. semantic | var(--wl-dimension-tag-height) | 20px |
--wl-metric-chip-heightRole chip-height. semantic | var(--wl-dimension-chip-height) | 30px |
--wl-metric-badge-heightRole badge-height. semantic | var(--wl-dimension-badge-height) | 18px |
--wl-corner-surfaceRole card-radius. semantic | var(--wl-radius) | 8px |
--wl-corner-overlayRole dialog-radius. semantic | var(--wl-radius-xl) | 12px |
--wl-layout-drawer-widthRole drawer-width. semantic | var(--wl-dimension-drawer-width) | 420px |
--wl-layer-maskRole mask-z. semantic | var(--wl-dimension-mask-z) | 70 |
--wl-layer-popoverRole overlay-z. semantic | var(--wl-dimension-overlay-z) | 80 |
--wl-metric-iconbtn-size-smRole iconbtn-size-sm. semantic | var(--wl-dimension-iconbtn-size-sm) | 30px |
--wl-metric-sidebar-widthRole sidebar-width. semantic | var(--wl-dimension-sidebar-width) | 68px |
--wl-metric-sidebar-expanded-widthRole sidebar-expanded-width. semantic | var(--wl-dimension-sidebar-expanded-width) | 228px |
--wl-layer-navigationRole sidebar-z. semantic | var(--wl-dimension-sidebar-z) | 40 |
--wl-layer-navigation-modalRole sidebar-mobile-z. semantic | var(--wl-dimension-sidebar-mobile-z) | 90 |
--wl-metric-segmented-shadowRole segmented-shadow. semantic | 0 1px 2px var(--wl-ink-a08) | 0 1px 2px rgba(20, 22, 28, 0.08) |
--wl-layout-menu-widthRole menu-width. semantic | var(--wl-dimension-menu-width) | 230px |
--wl-layout-popover-widthRole popover-width. semantic | var(--wl-dimension-popover-width) | 280px |
--wl-layout-command-widthRole command-width. semantic | var(--wl-dimension-command-width) | 580px |
--wl-metric-command-max-heightRole command-max-height. semantic | var(--wl-dimension-command-max-height) | 420px |
--wl-metric-command-input-heightRole command-input-height. semantic | var(--wl-dimension-command-input-height) | 52px |
--wl-layer-commandRole command-z. semantic | var(--wl-dimension-command-z) | 90 |
--wl-metric-page-header-gapRole page-header-gap. semantic | var(--wl-dimension-page-header-gap) | 16px |
--wl-metric-filter-bar-gapRole filter-bar-gap. semantic | var(--wl-dimension-filter-bar-gap) | 10px |
--wl-metric-filter-drawer-widthRole filter-drawer-width. semantic | var(--wl-dimension-filter-drawer-width) | 380px |
--wl-layer-filterRole filter-z. semantic | var(--wl-dimension-filter-z) | 90 |
--wl-layer-toastRole toast-z. semantic | var(--wl-dimension-toast-z) | 100 |
--wl-status-info-iconRole toast-icon-info. semantic | var(--wl-blue-300) | #9cbdf0 |
--wl-status-success-iconRole toast-icon-ok. semantic | var(--wl-green-400) | #4cc38a |
--wl-status-warning-iconRole toast-icon-warn. semantic | var(--wl-amber-400) | #e0a93e |
--wl-status-danger-iconRole toast-icon-err. semantic | var(--wl-palette-danger) | #ab4448 |
--wl-metric-stepper-input-widthRole stepper-input-width. semantic | var(--wl-dimension-stepper-input-width) | 76px |
--wl-metric-stepper-btn-widthRole stepper-btn-width. semantic | var(--wl-dimension-stepper-btn-width) | 36px |
--wl-metric-slider-heightRole slider-height. semantic | var(--wl-dimension-slider-height) | 6px |
--wl-metric-slider-thumbRole slider-thumb. semantic | var(--wl-dimension-slider-thumb) | 18px |
--wl-metric-step-numRole step-num. semantic | var(--wl-dimension-step-num) | 24px |
--wl-metric-table-cell-pad-yRole table-cell-pad-y. semantic | var(--wl-dimension-table-cell-pad-y) | 11px |
--wl-metric-table-cell-pad-xRole table-cell-pad-x. semantic | var(--wl-dimension-table-cell-pad-x) | 12px |
--wl-metric-table-head-pad-yRole table-head-pad-y. semantic | var(--wl-dimension-table-head-pad-y) | 8px |
--wl-metric-stat-bar-heightRole stat-bar-height. semantic | var(--wl-dimension-stat-bar-height) | 3px |
--wl-metric-colorpick-swRole colorpick-sw. semantic | var(--wl-dimension-colorpick-sw) | 26px |
--wl-metric-colorpick-sw-smRole colorpick-sw-sm. semantic | var(--wl-dimension-colorpick-sw-sm) | 20px |
--wl-metric-colorpick-gapRole colorpick-gap. semantic | var(--wl-dimension-colorpick-gap) | 8px |
--wl-metric-cal-gapRole cal-gap. semantic | var(--wl-dimension-cal-gap) | 5px |
--wl-metric-cal-day-min-hRole cal-day-min-h. semantic | var(--wl-dimension-cal-day-min-h) | 60px |
--wl-metric-cal-ev-sizeRole cal-ev-size. semantic | var(--wl-dimension-cal-ev-size) | 10.5px |
--wl-metric-dp-day-sizeRole dp-day-size. semantic | var(--wl-dimension-dp-day-size) | 30px |
--wl-metric-dp-panel-padRole dp-panel-pad. semantic | var(--wl-dimension-dp-panel-pad) | 12px |
--wl-metric-upload-drop-pad-yRole upload-drop-pad-y. semantic | var(--wl-dimension-upload-drop-pad-y) | 26px |
--wl-metric-upload-drop-pad-xRole upload-drop-pad-x. semantic | var(--wl-dimension-upload-drop-pad-x) | 20px |
--wl-space-noneSpacing none: 0 px. semantic | var(--wl-space-0) | 0px |
--wl-space-2xsSpacing 2xs: 2 px. semantic | var(--wl-space-2) | 2px |
--wl-space-xsSpacing xs: 4 px. semantic | var(--wl-space-4) | 4px |
--wl-space-smSpacing sm: 8 px. semantic | var(--wl-space-8) | 8px |
--wl-space-mdSpacing md: 12 px. semantic | var(--wl-space-12) | 12px |
--wl-space-lgSpacing lg: 16 px. semantic | var(--wl-space-16) | 16px |
--wl-space-xlSpacing xl: 24 px. semantic | var(--wl-space-24) | 24px |
--wl-space-2xlSpacing 2xl: 32 px. semantic | var(--wl-space-32) | 32px |
--wl-space-3xlSpacing 3xl: 48 px. semantic | var(--wl-space-48) | 48px |
--wl-space-4xlSpacing 4xl: 64 px. semantic | var(--wl-space-64) | 64px |
--wl-type-display-sizeMain introductory heading; one per screen. semantic | var(--wl-font-display-size) | 40px |
--wl-type-display-line-heightMain introductory heading; one per screen. semantic | var(--wl-font-display-line-height) | 48px |
--wl-type-display-weightMain introductory heading; one per screen. semantic | var(--wl-font-display-weight) | 700 |
--wl-type-display-familyMain introductory heading; one per screen. semantic | var(--wl-font-display-family) | "Gavia Sans Core", "Gavia Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif |
--wl-type-title-sizePage and large dialog title. semantic | var(--wl-font-title-size) | 28px |
--wl-type-title-line-heightPage and large dialog title. semantic | var(--wl-font-title-line-height) | 36px |
--wl-type-title-weightPage and large dialog title. semantic | var(--wl-font-title-weight) | 700 |
--wl-type-title-familyPage and large dialog title. semantic | var(--wl-font-title-family) | "Gavia Sans Core", "Gavia Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif |
--wl-type-heading-sizePage section and independent block. semantic | var(--wl-font-heading-size) | 20px |
--wl-type-heading-line-heightPage section and independent block. semantic | var(--wl-font-heading-line-height) | 28px |
--wl-type-heading-weightPage section and independent block. semantic | var(--wl-font-heading-weight) | 650 |
--wl-type-heading-familyPage section and independent block. semantic | var(--wl-font-heading-family) | "Gavia Sans Core", "Gavia Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif |
--wl-type-subheading-sizeCard, field group or small dialog. semantic | var(--wl-font-subheading-size) | 16px |
--wl-type-subheading-line-heightCard, field group or small dialog. semantic | var(--wl-font-subheading-line-height) | 24px |
--wl-type-subheading-weightCard, field group or small dialog. semantic | var(--wl-font-subheading-weight) | 600 |
--wl-type-subheading-familyCard, field group or small dialog. semantic | var(--wl-font-subheading-family) | "Gavia Sans Core", "Gavia Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif |
--wl-type-body-sizeDescriptions, instructions and form text. semantic | var(--wl-font-body-size) | 14px |
--wl-type-body-line-heightDescriptions, instructions and form text. semantic | var(--wl-font-body-line-height) | 20px |
--wl-type-body-weightDescriptions, instructions and form text. semantic | var(--wl-font-body-weight) | 400 |
--wl-type-body-familyDescriptions, instructions and form text. semantic | var(--wl-font-body-family) | "Gavia Sans Core", "Gavia Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif |
--wl-type-small-sizeCaptions and supporting text. semantic | var(--wl-font-small-size) | 12px |
--wl-type-small-line-heightCaptions and supporting text. semantic | var(--wl-font-small-line-height) | 18px |
--wl-type-small-weightCaptions and supporting text. semantic | var(--wl-font-small-weight) | 400 |
--wl-type-small-familyCaptions and supporting text. semantic | var(--wl-font-small-family) | "Gavia Sans Core", "Gavia Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif |
--wl-type-label-sizeShort action or field label. semantic | var(--wl-font-label-size) | 13px |
--wl-type-label-line-heightShort action or field label. semantic | var(--wl-font-label-line-height) | 20px |
--wl-type-label-weightShort action or field label. semantic | var(--wl-font-label-weight) | 500 |
--wl-type-label-familyShort action or field label. semantic | var(--wl-font-label-family) | "Gavia Sans Core", "Gavia Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif |
--wl-type-code-sizeTokens, identifiers and technical values. semantic | var(--wl-font-code-size) | 12px |
--wl-type-code-line-heightTokens, identifiers and technical values. semantic | var(--wl-font-code-line-height) | 18px |
--wl-type-code-weightTokens, identifiers and technical values. semantic | var(--wl-font-code-weight) | 400 |
--wl-type-code-familyTokens, identifiers and technical values. semantic | var(--wl-font-code-family) | "SF Mono", ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace |
--wl-text-mutedSupporting text with contrast of at least 4.5:1. semantic | var(--wl-text-2) | #5b6470 |
--wl-focus-colorVisible keyboard focus outline. semantic | var(--wl-accent) | #3c7490 |
--wl-focus-invalid-colorFocus outline for an invalid field. semantic | var(--wl-danger-hover) | #963c3f |
--wl-text-dangerReadable error text on the primary surface. semantic | var(--wl-err-text) | #ab4448 |
--wl-action-primary-bgPrimary action surface. semantic | var(--wl-palette-action-primary-bg) | #3c7490 |
--wl-action-primary-hoverPrimary action on hover. semantic | var(--wl-palette-action-primary-hover) | #376b84 |
--wl-action-primary-textReadable primary action text. semantic | var(--wl-palette-action-primary-text) | #ffffff |
--wl-action-danger-bgDestructive action surface. semantic | var(--wl-palette-danger) | #ab4448 |
--wl-action-danger-hoverDestructive action on hover. semantic | var(--wl-palette-danger-hover) | #963c3f |
--wl-action-danger-textReadable destructive action text. semantic | var(--wl-on-accent) | #ffffff |
--wl-layout-page-maxPage container width. semantic | var(--wl-size-page-max) | 1240px |
--wl-layout-page-gutterContent spacing from the page edge. semantic | var(--wl-space-xl) | 24px |
--wl-layout-grid-gapResponsive grid gap. semantic | var(--wl-space-lg) | 16px |
--wl-elevation-surfaceSubtle card elevation. semantic | var(--wl-shadow-card) | 0 1px 3px rgba(15, 26, 35, 0.05) |
--wl-elevation-floatingFloating surface elevation. semantic | var(--wl-shadow-pop) | 0 4px 14px rgba(15, 26, 35, 0.08), 0 16px 44px rgba(15, 26, 35, 0.12) |
--wl-motion-fastButton and field feedback. semantic | var(--wl-dur-2) | 0.12s |
--wl-motion-normalSmall panel entrance. semantic | var(--wl-dur-4) | 0.18s |
--wl-motion-slowLarge modal transition. semantic | var(--wl-dur-5) | 0.2s |
--wl-motion-easeMotion curve. semantic | var(--wl-ease) | ease |
--wl-layout-reading-maxNarrow container width for articles and instructions. semantic | var(--wl-size-reading-max) | 72ch |
--wl-layout-grid-minMinimum wl-grid column width; capped at 100% inside a narrow container. semantic | var(--wl-size-grid-min) | 240px |
--wl-layer-baseRegular positioned content layer within its stacking context. semantic | var(--wl-z-base) | 0 |
--wl-layer-stickySticky header or navigation layer; does not escape its parent stacking context. semantic | var(--wl-z-sticky) | 50 |
--wl-action-primary-activePressed primary action surface. semantic | var(--wl-palette-action-primary-active) | #326179 |
--wl-accent-warmRestrained warm accent for illustrations, decorative markers and small details. semantic | var(--wl-palette-accent-warm) | #948775 |
--wl-accent-warm-softDecorative warm highlight background; use the primary text role for text. semantic | var(--wl-palette-accent-warm-soft) | #efebe3 |
--wl-corner-buttonDefault button radius, independent of fields and cards. semantic | var(--wl-dimension-btn-radius) | 4px |
--wl-corner-button-smRadius for xs and sm buttons. semantic | var(--wl-dimension-btn-radius-sm) | 3px |
--wl-corner-button-lgRadius for lg buttons. semantic | var(--wl-dimension-btn-radius-lg) | 5px |
05 / Components
Props, models, events and slots come from the library manifest. Size and density control geometry; variant conveys the action's meaning; states reflect availability.
Use one primary action per group. Danger is for irreversible actions with confirmation. Loading prevents repeated submissions; explain disabled actions nearby.
Action button with variants, sizes and loading state.
| Prop | Type / values | Purpose |
|---|---|---|
variant | primary | secondary | ghost | soft | link | Visual button variant. Default: "secondary" |
tone | neutral | accent | info | success | warning | danger | Semantic color, independent of the visual treatment. |
size | xs | sm | md | lg | Button size. Default: "md" |
density | default | compact | Compact mode reduces button height. Default: "default" |
loading | boolean | Shows loading instead of the icon and blocks clicks. Default: false |
disabled | boolean | Disables the button. Default: false |
block | boolean | Fill the container width. Default: false |
type | button | submit | reset | Native button type. Default: "button" |
pt | object | Attributes of internal Gavia UI elements. |
as | union | Render a link explicitly; href or to also selects link rendering. |
href | string | Link destination, forwarded to WlLink. |
to | union | Application router destination handled by the configured link adapter. |
iconPosition | union | Place the icon before or after the default slot. |
default — Text or other button content.icon — Leading icon (hidden while loading).click — MouseEventClicks: 0
Check hover and clicks with a mouse, and focus with the keyboard. Selection, opening and closing update v-model. Settings above can be combined.
<script setup lang="ts">
import { WlButton, WlIcon } from "gavia-ui";
import { ref } from "vue";
const count = ref(0);
</script>
<template>
<div class="wl-stack" data-space="md">
<div>
<WlButton @click="count++"><template #icon><WlIcon name="plus" /></template>{{ "Add" }}</WlButton>
</div>
<p role="status" class="wl-text-small">{{ "Clicks:" }} {{ count }}</p>
</div>
</template>06 / Patterns
Components handle rendering and interaction. The application owns validation, requests and business rules. Examples on this page work without a server.
WlField provides id, invalid and ariaDescribedby through a scoped slot. The placeholder supplements the label.
| Material | State |
|---|---|
| Typography rules | Ready |
| Token catalog | Ready |
| Component contracts | Ready |
Drawer opens over the page. Escape closes the panel and restores focus to the trigger. Configure animation for the application or an individual panel.
07 / Recipes
Six examples with copyable code: data states, cancellation, errors and retries. Workflows are checked alongside components.
Search, filters, sorting and pagination. Create and edit in Drawer, confirm deletion and show Toast notifications.
| Material | Status | Actions |
|---|---|---|
| Access and roles | In progress | |
| Access and roles · 12 | In progress | |
| Access and roles · 8 | In progress | |
| Research plan | In progress |
Found: 12 · page 1 of 3
<script setup lang="ts">
import { useWlConfirm, useWlToast, WlButton, WlDrawer, WlEmpty, WlField, WlFilterBar, WlInput, WlPageHeader, WlPagination, WlPill, WlSelect, WlTable, type WlTableColumn, WlTag } from "gavia-ui";
import { computed, nextTick, ref, watch } from "vue";
type Material = { id: number; title: string; status: string };
const materials = ref<Material[]>(Array.from({ length: 12 }, (_, index) => ({
id: index + 1, title: ["Team guide", "Research plan", "Review guidelines", "Access and roles"][index % 4] + (index < 4 ? "" : ` · ${index + 1}`),
status: index % 2 ? "progress" : "done"
})));
const search = ref("");
const status = ref<string | null>(null);
const order = ref<string | null>("asc");
const page = ref(1);
const confirm = useWlConfirm();
const toast = useWlToast();
const filtered = computed(() => materials.value.filter((item) =>
item.title.toLocaleLowerCase("ru").includes(search.value.toLocaleLowerCase("ru")) && (!status.value || item.status === status.value))
.sort((a, b) => a.title.localeCompare(b.title, "ru") * (order.value === "desc" ? -1 : 1)));
const pageCount = computed(() => Math.max(1, Math.ceil(filtered.value.length / 4)));
const visibleRows = computed(() => filtered.value.slice((page.value - 1) * 4, page.value * 4));
watch([search, status, order], () => {
page.value = 1;
});
watch(pageCount, (count) => {
page.value = Math.min(page.value, count);
});
const columns: readonly WlTableColumn<Material>[] = [{ key: "title", label: "Material" }, { key: "status", label: "Status" }, { key: "actions", label: "Actions", kind: "virtual" }];
function clear(): void {
search.value = "";
status.value = null;
}
const editing = ref(false);
const editingId = ref<number | null>(null);
const title = ref("");
const titleError = ref("");
let nextId = 13;
function edit(item?: Material): void {
editingId.value = item?.id ?? null;
title.value = item?.title ?? "";
titleError.value = "";
editing.value = true;
}
async function save(): Promise<void> {
titleError.value = title.value.trim() ? "" : "Enter a title.";
if (titleError.value) {
await nextTick();
document.getElementById("recipe-material-title")?.focus();
return;
}
const current = materials.value.find((item) => item.id === editingId.value);
if (current) current.title = title.value.trim();
else materials.value.push({ id: nextId++, title: title.value.trim(), status: "progress" });
editing.value = false;
toast.ok("Material saved");
}
function remove(id: number): void {
const item = materials.value.find((entry) => entry.id === id);
if (!item) return;
confirm.confirmDanger({
header: "Delete material?", message: `«${item.title}${"» will be deleted."}`, acceptLabel: "Delete", rejectLabel: "Cancel",
accept: () => {
materials.value = materials.value.filter((entry) => entry.id !== id);
toast.ok("Material deleted");
}
});
}
</script>
<template>
<section class="wl-stack" data-space="lg" aria-label="Material list">
<WlPageHeader
title="Team materials"
description="Documents, agreements and research."
:heading-level="2"
size="md"
>
<template #meta><WlTag>{{ materials.length }} {{ "materials" }}</WlTag></template>
<template #actions><WlButton variant="primary" @click="edit()">{{ "Create material" }}</WlButton></template>
</WlPageHeader>
<WlInput
v-model="search"
type="search"
placeholder="Find a material"
aria-label="Find a material"
/>
<WlFilterBar
:active-count="Number(Boolean(status))"
aria-label="Material filters"
panel-title="Material filters"
@clear="clear"
>
<WlSelect
v-model="status"
:options="[{ label: 'Done', value: 'done' }, { label: 'In progress', value: 'progress' }]"
option-label="label"
option-value="value"
aria-label="Material status"
placeholder="All statuses"
/>
<WlSelect
v-model="order"
:options="[{ label: 'Name A–Z', value: 'asc' }, { label: 'Name Z–A', value: 'desc' }]"
option-label="label"
option-value="value"
aria-label="Material order"
/>
<template #summary><WlTag
v-if="status"
removable
remove-label="Remove status filter"
@remove="status = null"
>{{ status === 'done' ? "Done" : "In progress" }}</WlTag></template>
</WlFilterBar>
<div
v-if="filtered.length"
class="ds-material-table"
role="region"
aria-label="Team materials"
tabindex="0"
><WlTable :columns="columns" :value="visibleRows" :pt="{ root: { style: { minWidth: '640px' } } }">
<template #cell-status="{ value }">
<WlPill
:tone="value === 'done' ? 'success' : 'info'"
:label="value === 'done' ? 'Done' : 'In progress'"
/>
</template>
<template #cell-actions="{ row }"><div class="wl-inline" data-space="xs"><WlButton
size="sm"
variant="ghost"
:aria-label="`${'Edit '}${row.title}`"
@click="edit(materials.find((item) => item.id === row.id))"
>{{ "Edit" }}</WlButton><WlButton
size="sm"
variant="ghost"
tone="danger"
:aria-label="`${'Delete '}${row.title}`"
@click="remove(Number(row.id))"
>{{ "Delete" }}</WlButton></div></template>
</WlTable></div>
<WlEmpty
v-else
icon="search"
title="No materials found"
description="Change the query or clear the filters."
><template #action><WlButton @click="clear">{{ "Clear search" }}</WlButton></template></WlEmpty>
<div class="wl-inline" data-space="md">
<WlPagination v-if="filtered.length" v-model:page="page" :page-count="pageCount" />
<p class="wl-text-small wl-text-muted" role="status">
{{ "Found:" }} {{ filtered.length }} {{ "· page" }}
{{ page }} {{ "of" }} {{ pageCount }}
</p>
</div>
<WlDrawer v-model:visible="editing" :header="editingId ? 'Edit material' : 'New material'">
<form
id="recipe-material-form"
class="wl-stack"
data-space="lg"
@submit.prevent="save"
>
<WlField
id="recipe-material-title"
v-slot="field"
label="Title"
required
:error="titleError"
><WlInput
:id="field.id"
v-model="title"
:invalid="field.invalid"
:aria-describedby="field.ariaDescribedby"
maxlength="120"
/></WlField>
<p class="wl-text-small wl-text-muted">{{ "To apply changes, click Save." }}</p>
</form>
<template #footer>
<WlButton variant="ghost" @click="editing = false">{{ "Cancel" }}</WlButton>
<WlButton variant="primary" type="submit" form="recipe-material-form">{{ "Save" }}</WlButton>
</template>
</WlDrawer>
</section>
</template>
<style scoped>
.ds-material-table { overflow: auto; max-width: 100%; }
</style>Examples use sample data. Saving and loading are simulated in the browser. Connect server requests in your application. The workflow and copyable code share one Vue file.
// main.ts — Import CSS explicitly; set the theme using data-wl-theme on html.
import { createApp } from "vue";
import { WlConfig, WlToastService, WlConfirmationService, createWlPt, wlLocaleEn } from "gavia-ui";
import "gavia-ui/styles/reset.css";
import "gavia-ui/styles/base.css";
import "gavia-ui/styles/primitives.css";
import "gavia-ui/themes/white.css";
import "gavia-ui/themes/graphite.css";
import "gavia-ui/themes/newspaper.css";
import "gavia-ui/styles/fonts/gavia.css";
import "gavia-ui/themes/gavia.css";
import "gavia-ui/themes/gavia-dark.css";
import App from "./App.vue";
document.documentElement.dataset.wlTheme = "white";
// WlConfig merges application pt with defaults; local component pt is applied last.
const pt = createWlPt({ button: { root: { "data-ui": "action" } } });
createApp(App).use(WlConfig, { locale: wlLocaleEn, motion: true, pt })
.use(WlToastService).use(WlConfirmationService).mount("#app");
// App.vue: import WlToast/WlConfirmDialog and mount one of each
// container alongside the page. Notifications and confirmations use them.
// <template><YourPage /><WlToast /><WlConfirmDialog /></template>08 / Complex content
Long text, eight tags, 80 options, 20 table rows, date limits and nested overlays. Change the window width and text size.
| Long name | Identifier |
|---|---|
| Agreements for an international distributed team preparing materials and conducting research together · 1 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 2 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 3 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 4 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 5 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 6 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 7 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 8 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 9 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 10 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 11 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 12 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 13 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 14 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 15 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 16 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 17 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 18 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 19 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
| Agreements for an international distributed team preparing materials and conducting research together · 20 | research_material_without_spaces_2026_10_01_abcdefghijklmnopqrstuvwxyz |
09 / Accessibility
Text contrast starts at 4.5:1; focus indicators at 3:1. The report checks the listed token pairs. Check custom colors and complete screens separately.
Tab moves focus. Enter / Space activates actions. Arrows change selection. Escape closes overlays. Focus stays visible and returns after closing.
motion: false disables transitions. The system prefers-reduced-motion setting applies to every theme. Animation does not change the meaning or lifetime of data.
Give icon actions an aria-label. Errors should explain the next step. Aim for 44 × 44 px touch targets; use lg with sufficient spacing.
| Pair / gavia | Actual | Minimum |
|---|---|---|
| Body text | 16.73:1 | 4.5:1 |
| Supporting text | 5.7:1 | 4.5:1 |
| Text on a secondary surface | 15.63:1 | 4.5:1 |
| Primary action | 5.13:1 | 4.5:1 |
| Primary action: hover | 5.84:1 | 4.5:1 |
| Primary action: active | 6.73:1 | 4.5:1 |
| Destructive action | 5.75:1 | 4.5:1 |
| Destructive action: hover | 6.97:1 | 4.5:1 |
| Tooltip | 17.61:1 | 4.5:1 |
| Information message | 7.8:1 | 4.5:1 |
| Success message | 5.84:1 | 4.5:1 |
| Warning | 5.73:1 | 4.5:1 |
| Error message | 5.04:1 | 4.5:1 |
| Focus on the page | 4.87:1 | 3:1 |
| Link on the page | 6.39:1 | 4.5:1 |
| Focus on a secondary surface | 4.55:1 | 3:1 |
| Text on a raised surface | 17.33:1 | 4.5:1 |
| Supporting text on a raised surface | 5.9:1 | 4.5:1 |
| Focus on a raised surface | 5.05:1 | 3:1 |
| Accent text on a raised surface | 6.62:1 | 4.5:1 |
| Accent text on a secondary surface | 5.97:1 | 4.5:1 |
| Accent text on a selected surface | 5.41:1 | 4.5:1 |
| Accent text on a soft surface: hover | 4.95:1 | 4.5:1 |
New tokens pass tokens:check. Patterns are checked in Chromium, Firefox, WebKit and mobile Chromium. Before release, types, the package archive and an isolated consumer are checked too.