Light
Red, black and white.

Build interfaces with native HTML, VEXARO red and an API that grows with your project.
Get started View dashboard Explore iconsVEXARO UI is a CSS-first library with no frameworks. The default identity uses red, black and white, with square corners on all components. Green and amber indicate success and warning.
Use semantic HTML, native CSS and JavaScript only for interaction. Version 1.0.0 delivers every component from VEX-UI-001 through VEX-UI-018, including advanced controls and optional integration.
Target compatibility: current Chrome, Firefox, Edge and Safari versions with CSS Grid, dialog.showModal(), inert and ES Modules. Datepicker uses the browser's native calendar; appearance and shortcuts vary by platform. See the public contract and the changes.
Copy the two files from dist/ into your project. They are independent of the sources. On a static server, use:
<!doctype html>
<html lang="en" data-vx-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://ui.vexaro.solutions/dist/vexaro.css">
<script type="module" src="https://ui.vexaro.solutions/dist/vexaro.js"></script>
<title>My VEXARO project</title>
</head>
<body>
<main class="vx-container vx-py-8">
<h1>Meu projeto</h1>
<button class="vx-btn" type="button" data-vx-toggle="toast"
data-vx-message="Ready!">Test notification</button>
</main>
</body>
</html>To open directly via file://, or to integrate with classic scripts, replace the script with:
<script defer src="https://ui.vexaro.solutions/dist/vexaro.global.js"></script>The classic bundle exposes window.Vexaro. Both versions initialize the attributes data-vx-* automatically. Load only one of them. Alternatively, use css/vexaro.css and js/vexaro.js, preserving the source folders.
The reset/reboot is global: it normalizes box-sizing, margins, controls, typography and focus. Components, utilities and tokens use the prefix vx-.
Semantic tokens follow the theme. The primary red is #b91c1c in light mode and #ff3333 in dark mode. Use --vx-color-primary instead of fixing a primitive color. All border radii are 0 by default.
| Token | Value | Preview |
|---|---|---|
--vx-white | #ffffff | |
--vx-black | #000000 | |
--vx-neutral-50 | #fafafa | |
--vx-neutral-100 | #f5f5f5 | |
--vx-neutral-200 | #e5e5e5 | |
--vx-neutral-400 | #a3a3a3 | |
--vx-neutral-500 | #737373 | |
--vx-neutral-600 | #525252 | |
--vx-neutral-900 | #171717 | |
--vx-red-400 | #f87171 | |
--vx-red-500 | #ff3333 | |
--vx-red-700 | #b91c1c | |
--vx-slate-50 | #f8fafc | |
--vx-slate-100 | #f1f5f9 | |
--vx-slate-200 | #e2e8f0 | |
--vx-slate-400 | #94a3b8 | |
--vx-slate-500 | #64748b | |
--vx-slate-600 | #475569 | |
--vx-slate-800 | #1e293b | |
--vx-slate-900 | #0f172a | |
--vx-blue-300 | #93c5fd | |
--vx-blue-700 | #1d4ed8 | |
--vx-green-300 | #86efac | |
--vx-green-800 | #166534 | |
--vx-amber-300 | #fcd34d | |
--vx-amber-900 | #78350f | |
--vx-red-300 | #fca5a5 | |
--vx-red-800 | #991b1b |
| Token | Value | Preview |
|---|---|---|
--vx-space-0 | 0 | |
--vx-space-1 | 0.25rem | |
--vx-space-2 | 0.5rem | |
--vx-space-3 | 0.75rem | |
--vx-space-4 | 1rem | |
--vx-space-5 | 1.25rem | |
--vx-space-6 | 1.5rem | |
--vx-space-8 | 2rem | |
--vx-space-10 | 2.5rem | |
--vx-space-12 | 3rem | |
--vx-space-16 | 4rem |
| Token | Value | Preview |
|---|---|---|
--vx-font-sans | system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif | |
--vx-font-mono | ui-monospace, SFMono-Regular, Consolas, monospace | |
--vx-text-xs | 0.75rem | |
--vx-text-sm | 0.875rem | |
--vx-text-md | 1rem | |
--vx-text-lg | 1.125rem | |
--vx-text-xl | 1.25rem | |
--vx-text-2xl | 1.5rem | |
--vx-text-3xl | 2rem | |
--vx-text-4xl | 2.5rem | |
--vx-font-normal | 400 | |
--vx-font-medium | 500 | |
--vx-font-semibold | 600 | |
--vx-font-bold | 700 | |
--vx-leading-tight | 1.25 | |
--vx-leading-normal | 1.5 | |
--vx-leading-relaxed | 1.75 |
| Token | Value | Preview |
|---|---|---|
--vx-border-width | 1px | |
--vx-border-width-thick | 2px | |
--vx-radius-none | 0 | |
--vx-radius-sm | 0 | |
--vx-radius-md | 0 | |
--vx-radius-lg | 0 | |
--vx-radius-pill | 0 | |
--vx-focus-width | 3px | |
--vx-focus-offset | 3px |
| Token | Value | Preview |
|---|---|---|
--vx-shadow-none | none | |
--vx-shadow-sm | 0 1px 3px rgb(0 0 0 / 0.12) | |
--vx-shadow-md | 0 4px 12px rgb(0 0 0 / 0.16) | |
--vx-shadow-lg | 0 12px 32px rgb(0 0 0 / 0.2) |
| Token | Light | Dark |
|---|---|---|
--vx-color-bg | var(--vx-white) | var(--vx-black) |
--vx-color-surface | var(--vx-white) | var(--vx-neutral-900) |
--vx-color-surface-muted | var(--vx-neutral-100) | var(--vx-black) |
--vx-color-text | var(--vx-black) | var(--vx-white) |
--vx-color-text-muted | var(--vx-neutral-600) | var(--vx-neutral-200) |
--vx-color-border | var(--vx-neutral-500) | var(--vx-neutral-400) |
--vx-color-border-subtle | var(--vx-neutral-200) | var(--vx-neutral-600) |
--vx-color-primary | var(--vx-red-700) | var(--vx-red-500) |
--vx-color-on-primary | var(--vx-white) | var(--vx-black) |
--vx-color-success | var(--vx-green-800) | var(--vx-green-300) |
--vx-color-on-success | var(--vx-white) | var(--vx-black) |
--vx-color-warning | var(--vx-amber-900) | var(--vx-amber-300) |
--vx-color-on-warning | var(--vx-white) | var(--vx-black) |
--vx-color-danger | var(--vx-red-800) | var(--vx-red-300) |
--vx-color-on-danger | var(--vx-white) | var(--vx-black) |
--vx-color-focus | var(--vx-red-700) | var(--vx-red-500) |
Light is the default theme. Set the attribute on HTML or a container. Local themes keep their colors when the page theme changes.
JavaScript: no
Red, black and white.
Bold red on black.
<div class="vx-grid">
<article class="vx-card vx-col-12 vx-col-md-6" data-vx-theme="light">
<div class="vx-card-body"><h3>Light</h3><p>Red, black and white.</p><button class="vx-btn" type="button">Primary action</button></div>
</article>
<article class="vx-card vx-col-12 vx-col-md-6" data-vx-theme="dark">
<div class="vx-card-body"><h3>Dark</h3><p>Bold red on black.</p><button class="vx-btn" type="button">Primary action</button></div>
</article>
</div>| Class / attribute | Usage |
|---|---|
| data-vx-theme="light|dark" | Global or local theme; works without JS. |
| data-vx-toggle="theme" | Toggle button; requires JS. |
Load your CSS after the library and override color pairs together. Explicit themes reset colors in their own scope; customize each required selector.
:root, [data-vx-theme="light"] {
--vx-color-primary: #a31515;
--vx-color-on-primary: #ffffff;
}
[data-vx-theme="dark"] {
--vx-color-primary: #ff3333;
--vx-color-on-primary: #000000;
}Blue and slate primitives remain available for compatibility; the default identity uses red and neutrals. Palette changes require contrast revalidation. Scales in rem follow the root font size.
CSS Grid for two-dimensional layouts. Start with 12 columns on mobile and override spans at larger breakpoints.
JavaScript: no
<div class="vx-container-fluid">
<div class="vx-grid vx-gap-4">
<div class="vx-card vx-col-12 vx-col-md-4 vx-p-4">4 columns on tablets</div>
<div class="vx-card vx-col-12 vx-col-md-8 vx-p-4">8 columns on tablets</div>
</div>
</div>| Class / attribute | Usage |
|---|---|
| vx-container / vx-container-fluid | Responsive maximum width / full width, with side padding. |
| vx-grid / vx-grid-cols-12 | 12-column grid; default gap is space-4. |
| vx-col-1 … vx-col-12 | Number of occupied columns. |
| vx-col-{sm|md|lg|xl|xxl}-{1…12} | Spans by minimum breakpoint. |
| Breakpoints | sm 576px · md 768px · lg 992px · xl 1200px · xxl 1400px |
| Containers | 540 / 720 / 960 / 1140 / 1320px at the corresponding breakpoints. |
Use Flexbox for action bars and one-dimensional alignment.
JavaScript: no
<div class="vx-flex vx-flex-wrap vx-items-center vx-justify-between vx-gap-4 vx-w-full">
<span class="vx-badge vx-badge-success">3 changes saved</span>
<div class="vx-flex vx-gap-2"><button type="button" class="vx-btn vx-btn-secondary">Cancel</button><button type="button" class="vx-btn">Save</button></div>
</div>| Class / attribute | Usage |
|---|---|
| vx-flex / vx-flex-row / vx-flex-col | Display and direction. |
| vx-flex-wrap / vx-flex-nowrap / vx-flex-1 / vx-flex-none | Wrapping and growth. |
| vx-justify-start|end|center|between|around | Main-axis alignment. |
| vx-items-start|end|center|stretch / vx-self-center | Cross-axis alignment. |
Use button for actions and links for navigation. Visible labels explain the action.
JavaScript: no
<button class="vx-btn vx-btn-primary" type="button">Primary</button>
<button class="vx-btn vx-btn-secondary" type="button">Secondary</button>
<button class="vx-btn vx-btn-outline" type="button">Outline</button>
<button class="vx-btn vx-btn-ghost" type="button">Ghost</button>
<button class="vx-btn vx-btn-success" type="button">Confirm</button>
<button class="vx-btn vx-btn-danger" type="button">Delete</button>
<button class="vx-btn vx-btn-sm" type="button">Small</button>
<button class="vx-btn vx-btn-lg" type="button">Large</button>
<button class="vx-btn vx-btn-rounded" type="button">Rounded</button>
<button class="vx-btn vx-btn-pill" type="button">Pill</button>
<button class="vx-btn vx-btn-outline vx-btn-pill" type="button">Outline pill</button>
<button class="vx-btn" type="button" disabled>Unavailable</button>
<button class="vx-btn" type="button" aria-busy="true" disabled>Saving…</button>| Class / attribute | Usage |
|---|---|
| vx-btn | Base; medium size. |
| vx-btn-primary|secondary|outline|ghost|success|danger | Semantic variants. |
| vx-btn-sm / vx-btn-lg | Sizes. |
| vx-btn-rounded / vx-btn-pill | Rounded corners (.5rem) or a pill shape; works with any variant. |
| disabled / aria-disabled="true" | Native blocking / semantics. VEXARO JS prevents clicks on aria-disabled; without JS, your application must prevent link activation. |
| aria-busy="true" | Communicates loading state to assistive technology. Also update the text to “Saving…” and use disabled to block repeated actions. |
| --vx-color-primary / --vx-radius-md | Primary color and radius (0 by default). |
Use SVGs from the icon catalog inside the button. The stroke inherits text color through currentColor. Icons alongside text are decorative; icon-only buttons need an accessible name in aria-label.
<button class="vx-btn" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M3 3h14l4 4v14H3Z"/><path d="M7 3v6h9V3"/><path d="M7 21v-7h10v7"/></svg>
Save
</button>
<button class="vx-btn vx-btn-secondary" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 3v12"/><path d="m7 10 5 5 5-5"/><path d="M4 16v5h16v-5"/></svg>
Download
</button>
<button class="vx-btn vx-btn-outline" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 4v16"/><path d="M4 12h16"/></svg>
Add
</button>
<button class="vx-btn vx-btn-danger" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M3 6h18"/><path d="M9 6V3h6v3"/><path d="m5 6 1 15h12l1-15"/><path d="M10 10v7"/><path d="M14 10v7"/></svg>
Delete
</button>
<button class="vx-btn vx-btn-secondary" type="button" aria-label="Settings">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M9 3h6l1 4 4 1v8l-4 1-1 4H9l-1-4-4-1V8l4-1Z"/><path d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/></svg>
</button>Reusable compositions made only with VEXARO UI components. Choose one of 10 models, copy the HTML and connect your data.
Production capacity
3 active regions
Last 30 days
<div class="vx-dashboard">
<header class="vx-dashboard-header">
<div><h3 class="vx-dashboard-title">Infrastructure</h3><p class="vx-dashboard-subtitle">Production capacity</p></div>
<button class="vx-btn" type="button">New server</button>
</header>
<div class="vx-dashboard-metrics">
<article class="vx-card vx-stat"><h3 class="vx-stat-label">Servers</h3><strong class="vx-stat-value">12</strong><p class="vx-stat-description">3 active regions</p></article>
<article class="vx-card vx-stat"><h3 class="vx-stat-label">Availability</h3><strong class="vx-stat-value">99.98%</strong><p class="vx-stat-description">Last 30 days</p></article>
</div>
<section class="vx-card"><div class="vx-card-body vx-progress-group"><label for="dashboard-ram">RAM · 68%</label><progress class="vx-progress" id="dashboard-ram" value="68" max="100">68%</progress></div></section>
</div>Load only https://ui.vexaro.solutions/dist/vexaro.css and, for modal, tabs and table, https://ui.vexaro.solutions/dist/vexaro.global.js. The models share demo JavaScript only for exports and forms; no local CSS is needed.
Use vx-dashboard-split, vx-dashboard-equal or vx-dashboard-reverse to organize panels. The grid adapts to the available width. The variant vx-theme-black in <html data-vx-theme="dark"> applies deep black and subtle decorative borders without changing the light theme.
Native progress, with known or indeterminate value. No JavaScript required.
<div class="vx-w-full vx-dashboard-stack">
<div class="vx-progress-group">
<label class="vx-progress-label" for="demo-progress"><span>RAM</span><span>68%</span></label>
<progress class="vx-progress" id="demo-progress" value="68" max="100">68%</progress>
</div>
<progress class="vx-progress vx-progress-success vx-progress-sm" value="100" max="100" aria-label="Backup complete">100%</progress>
<progress class="vx-progress vx-progress-warning" value="80" max="100" aria-label="Storage used: 80%">80%</progress>
<progress class="vx-progress vx-progress-danger vx-progress-lg" value="95" max="100" aria-label="CPU usage: 95%">95%</progress>
<progress class="vx-progress" aria-label="Importing files"></progress>
</div>vx-progress: base; vx-progress-sm|lg: thickness; vx-progress-success|warning|danger: state. Use value and max; omit value when the duration is unknown. Always provide a label or aria-label. Adjust the color with the token --vx-progress-color. Animations respect reduced motion.
Explicit loading indicators: spinner, dots and skeleton. They are not added to buttons automatically.
<div class="vx-dashboard-stack vx-w-full">
<div class="vx-flex vx-items-center vx-gap-3" role="status">
<span class="vx-preloader" aria-hidden="true"></span><span>Loading data…</span>
</div>
<div class="vx-flex vx-items-center vx-gap-3" role="status">
<span class="vx-preloader vx-preloader-dots" aria-hidden="true"><span></span><span></span><span></span></span><span>Connecting to the server…</span>
</div>
<div class="vx-card vx-w-full" aria-busy="true" aria-label="Loading summary">
<div class="vx-card-body vx-dashboard-stack" aria-hidden="true">
<span class="vx-skeleton vx-skeleton-avatar"></span>
<span class="vx-skeleton vx-skeleton-title"></span>
<span class="vx-skeleton vx-skeleton-text"></span>
<span class="vx-skeleton vx-skeleton-text"></span>
</div>
</div>
</div>vx-preloader: spinner; vx-preloader-sm|lg: size; vx-preloader-dots: three dots; vx-skeleton and variants title|text|avatar: temporary structure. Use text in role="status" and mark the content awaiting an update with aria-busy="true". Once complete, remove the indicators and set aria-busy to false. No animation when the user prefers reduced motion.
Task list with native checkboxes, optional descriptions and completed states. Works with keyboard without JavaScript.
<ul class="vx-checklist vx-w-full">
<li><label class="vx-checklist-item"><input class="vx-checkbox" type="checkbox" checked><span class="vx-checklist-content"><span class="vx-checklist-title">Validate daily backup</span><span class="vx-checklist-description">Infrastructure · complete</span></span><span class="vx-badge vx-badge-success">Done</span></label></li>
<li><label class="vx-checklist-item"><input class="vx-checkbox" type="checkbox"><span class="vx-checklist-content"><span class="vx-checklist-title">Review permissions</span><span class="vx-checklist-description">Security · today</span></span><span class="vx-badge">High</span></label></li>
<li><label class="vx-checklist-item"><input class="vx-checkbox" type="checkbox" disabled><span class="vx-checklist-content"><span class="vx-checklist-title">Publish release</span><span class="vx-checklist-description">Awaiting review</span></span></label></li>
</ul>vx-checklist: list; vx-checklist-item: clickable label; vx-checklist-content, vx-checklist-title and vx-checklist-description: content. The visual state follows checked; disabled prevents changes. Persistence and business rules belong to your application.
The on/off control already available in the library, now with its own section, examples and documented states.
<fieldset class="vx-w-full">
<legend>Notification preferences</legend>
<label class="vx-check"><input class="vx-switch" type="checkbox" role="switch" checked>Incident alerts</label>
<label class="vx-check"><input class="vx-switch" type="checkbox" role="switch">Weekly report</label>
<label class="vx-check"><input class="vx-switch" type="checkbox" role="switch" disabled>Integration unavailable</label>
<label class="vx-check"><input class="vx-switch" type="checkbox" role="switch" checked disabled>Required policy</label>
</fieldset>Use input type="checkbox" role="switch" class="vx-switch" inside a label. checked sets the on state; disabled prevents changes. Tab moves focus and Space toggles the state. Read input.checked on the event change. Do not duplicate the state with aria-checked on a native input.
The component vx-theme-switch uses Vexaro sun and moon icons and keeps a rectangular shape. The state aria-checked="true" indicates dark theme; the UI JavaScript synchronizes the control with the theme. Enter and Space toggle the state.
<button class="vx-theme-switch" type="button" role="switch" aria-checked="false" aria-label="Dark theme" data-vx-toggle="theme">
<svg class="vx-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m5 5 1.5 1.5"/><path d="m17.5 17.5 1.5 1.5"/><path d="m5 19 1.5-1.5"/><path d="m17.5 6.5 1.5-1.5"/></svg>
<svg class="vx-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 14A9 9 0 0 1 10 3a8 8 0 0 0 11 11Z"/></svg>
</button>Short labels for categories and states. Information also appears in the text.
JavaScript: no
<span class="vx-badge">Draft</span>
<span class="vx-badge vx-badge-primary">Under review</span>
<span class="vx-badge vx-badge-success">Published</span>
<span class="vx-badge vx-badge-warning">Pending</span>
<span class="vx-badge vx-badge-danger">Failed</span>
<span class="vx-badge vx-badge-lg">Count: 12</span>| Class / attribute | Usage |
|---|---|
| vx-badge | Neutral base; static. |
| vx-badge-primary|success|warning|danger | Contextual color. |
| vx-badge-lg | Larger size. |
Group content with optional header, body and footer.
JavaScript: no
A shared foundation for applications and documentation.
<article class="vx-card vx-card-elevated">
<header class="vx-card-header"><h3>VEXARO project</h3></header>
<div class="vx-card-body"><span class="vx-badge vx-badge-success">Active</span><p class="vx-mt-4">A shared foundation for applications and documentation.</p></div>
<footer class="vx-card-footer"><a class="vx-btn" href="#quick-start">Start project</a></footer>
</article>| Class / attribute | Usage |
|---|---|
| vx-card / vx-card-header|body|footer | Surface and structure. |
| vx-card-elevated / vx-card-borderless / vx-card-outlined | Shadow, borderless or thick border. |
| --vx-color-surface / --vx-shadow-md / --vx-radius-lg | Appearance tokens. |
Static feedback needs no live region. For dynamic updates, use role="status" for routine messages and role="alert" only for urgency.
JavaScript: no
<div class="vx-alert vx-alert-info"><strong>Information:</strong> your session is active.</div>
<div class="vx-alert vx-alert-success"><strong>Success:</strong> changes saved.</div>
<div class="vx-alert vx-alert-warning"><strong>Warning:</strong> check the fields.</div>
<div class="vx-alert vx-alert-danger"><strong>Error:</strong> could not save.</div>| Class / attribute | Usage |
|---|---|
| vx-alert | Message with padding and border. |
| vx-alert-info|success|warning|danger | Variants combine contextual color and the corresponding on-* color. |
Native controls with associated labels and descriptions. HTML5 validation still works without JS.
JavaScript: no
<form class="vx-w-full" data-docs-form>
<div class="vx-field"><label class="vx-label" for="demo-name">Name</label><input class="vx-input" id="demo-name" name="name" required autocomplete="name" aria-describedby="name-help"><span class="vx-help" id="name-help">Enter your full name.</span></div>
<div class="vx-field"><label class="vx-label" for="demo-email">E-mail</label><input class="vx-input" type="email" id="demo-email" name="email" required autocomplete="email"></div>
<div class="vx-field"><label class="vx-label" for="demo-role">Profile</label><select class="vx-select" id="demo-role" name="role"><option>Designer</option><option>Developer</option></select></div>
<div class="vx-field"><label class="vx-label" for="demo-message">Message</label><textarea class="vx-textarea" id="demo-message" name="message"></textarea></div>
<fieldset class="vx-p-4 vx-mb-4"><legend>Preferences</legend>
<label class="vx-check"><input class="vx-checkbox" type="checkbox" name="news"> Receive updates</label>
<label class="vx-check"><input class="vx-radio" type="radio" name="plan" value="personal" checked> Personal</label>
<label class="vx-check"><input class="vx-radio" type="radio" name="plan" value="team"> Team</label>
<label class="vx-check"><input class="vx-switch" type="checkbox" role="switch" name="notifications"> Notifications</label>
</fieldset>
<div class="vx-field"><label class="vx-label" for="demo-invalid">Code with an error (static example)</label><input class="vx-input is-invalid" id="demo-invalid" value="ABC" aria-invalid="true" aria-describedby="code-error"><span class="vx-error" id="code-error">Use a six-digit code.</span></div>
<div class="vx-field"><label class="vx-label" for="demo-readonly">Read-only identifier</label><input class="vx-input" id="demo-readonly" value="VX-001" readonly></div>
<div class="vx-field"><label class="vx-label" for="demo-disabled">Unavailable field</label><input class="vx-input" id="demo-disabled" disabled value="No access"></div>
<button class="vx-btn" type="submit">Validate form</button>
</form>| Class / attribute | Usage |
|---|---|
| vx-field / vx-label / vx-help / vx-error | Group, label, help and error. |
| vx-input / vx-textarea / vx-select | Native controls. |
| vx-check / vx-checkbox / vx-radio / vx-switch | Selection and toggling. |
| :user-invalid / .is-invalid / .vx-validated :invalid | Error after interaction, explicit state or form validation. |
| aria-invalid / aria-describedby | Associate the error with the field and update the state in your application. |
Use caption, scoped headers and a focusable wrapper when horizontal scrolling is available.
JavaScript: no
| Name | Profile | Status |
|---|---|---|
| Ana | Design | Active |
| Bruno | Engineering | Pending |
| 2 members | ||
<div class="vx-table-responsive" role="region" aria-label="Team" tabindex="0">
<table class="vx-table vx-table-striped vx-table-bordered vx-table-hover vx-table-compact">
<caption>Team members</caption><thead><tr><th scope="col">Name</th><th scope="col">Profile</th><th scope="col">Status</th></tr></thead>
<tbody><tr><th scope="row">Ana</th><td>Design</td><td><span class="vx-badge vx-badge-success">Active</span></td></tr><tr><th scope="row">Bruno</th><td>Engineering</td><td><span class="vx-badge vx-badge-warning">Pending</span></td></tr></tbody>
<tfoot><tr><td colspan="3">2 members</td></tr></tfoot>
</table>
</div>| Class / attribute | Usage |
|---|---|
| vx-table | Base table. |
| vx-table-striped|bordered|hover|compact | Composable variants. |
| vx-table-responsive | Horizontal scrolling; tabindex=0 enables keyboard access. |
Real links represent destinations. The current page uses aria-current="page"; unavailable items have no href.
JavaScript: no
<nav aria-label="Breadcrumb navigation"><ol class="vx-breadcrumb"><li><a href="#inicio">Home</a></li><li><a href="#cards">Projects</a></li><li aria-current="page">VEXARO UI</li></ol></nav>
<nav aria-label="Example pages"><ol class="vx-pagination"><li><span aria-disabled="true">Previous</span></li><li><a href="#contextual" aria-current="page">1</a></li><li><a href="#tabelas">2</a></li><li><a href="#tabelas">Next</a></li></ol></nav>| Class / attribute | Usage |
|---|---|
| vx-breadcrumb / --vx-breadcrumb-separator | Breadcrumb; default separator "/". |
| vx-pagination | Semantic pagination. |
| aria-current="page" / aria-disabled="true" | Current page and unavailable item. |
| vx-tabs / vx-tab-item / vx-tab-link.is-active | Visual-only tabs; use links for destinations and the JS version for panels. |
A native dialog provides the top layer and isolates background content. The module manages focus, Tab, Escape, backdrop and return to the trigger.
JavaScript: yes
<button class="vx-btn" type="button" data-vx-toggle="modal" data-vx-target="#demo-modal">Open modal</button>
<dialog class="vx-modal" id="demo-modal" aria-labelledby="modal-title" tabindex="-1">
<header class="vx-modal-header"><h3 id="modal-title">New project</h3></header>
<div class="vx-modal-body"><label class="vx-label" for="project-name">Project name</label><input class="vx-input" id="project-name" autocomplete="off" autofocus></div>
<footer class="vx-modal-footer"><button class="vx-btn vx-btn-secondary" type="button" data-vx-dismiss="modal">Cancel</button><button class="vx-btn" type="button" data-vx-dismiss="modal">Finish</button></footer>
</dialog>| Class / attribute | Usage |
|---|---|
| dialog.vx-modal / vx-modal-header|body|footer | Dialog structure. |
| data-vx-toggle="modal" / data-vx-target | Declarative trigger. |
| data-vx-dismiss="modal" | Close button. |
| aria-labelledby | Accessible title ID. |
const modal = Vexaro.getInstance('#demo-modal');
modal.open();
modal.close();
// Events: vx:modal:open, vx:modal:closeDynamic notifications with a dismiss button. The timer pauses on hover or focus. Essential messages must remain available in the page content.
JavaScript: yes
<button class="vx-btn" type="button" data-vx-toggle="toast" data-vx-message="Project saved successfully.">Show notification</button>| Class / attribute | Usage |
|---|---|
| data-vx-toggle="toast" / data-vx-message | Creates a text message with a default five-second timer. |
| data-vx-toast / data-vx-target | Allows using an existing notification. |
| data-vx-dismiss="toast" | Manual dismissal. |
| role="status" / role="alert" | Routine / urgent announcement. |
const toast = Vexaro.Toast.show('Changes saved.', { duration: 5000 });
// duration: 0 keeps the message until dismissed; urgent: true uses alert.
toast.close();
// Events: vx:toast:open, vx:toast:closeAction menu with arrows, Home, End and Escape. Tab keeps natural navigation; clicking or moving focus outside closes the menu.
JavaScript: yes
<div class="vx-dropdown">
<button class="vx-btn" type="button" data-vx-toggle="dropdown" data-vx-target="#demo-menu" aria-controls="demo-menu" aria-expanded="false">Actions</button>
<div class="vx-dropdown-menu" id="demo-menu" hidden><a href="#cards">View projects</a><a href="#formularios">Edit profile</a><button type="button" data-vx-toggle="toast" data-vx-message="Demo link copied.">Share</button></div>
</div>| Class / attribute | Usage |
|---|---|
| vx-dropdown / vx-dropdown-menu | Container and floating list. |
| data-vx-toggle="dropdown" / data-vx-target | Declarative initialization. |
| aria-expanded / aria-haspopup | Managed by the module. |
const dropdown = Vexaro.getInstance('[data-vx-toggle="dropdown"]');
dropdown.open(); dropdown.close(); dropdown.toggle();
// Events: vx:dropdown:open, vx:dropdown:closeOnly one tab participates in the Tab order. Arrow keys switch tabs; Home and End go to the first and last. Each tab has its own panel.
JavaScript: yes
<div class="vx-w-full">
<div class="vx-tabs" role="tablist" aria-label="Project details" data-vx-tabs id="demo-tabs">
<button class="vx-tab-link" role="tab" type="button" id="tab-overview" aria-controls="panel-overview" aria-selected="true">Overview</button>
<button class="vx-tab-link" role="tab" type="button" id="tab-history" aria-controls="panel-history" aria-selected="false" tabindex="-1">History</button>
</div>
<div class="vx-tab-panel" role="tabpanel" id="panel-overview" aria-labelledby="tab-overview" tabindex="0">Ready to get started.</div>
<div class="vx-tab-panel" role="tabpanel" id="panel-history" aria-labelledby="tab-history" tabindex="0" hidden>Project created today.</div>
</div>| Class / attribute | Usage |
|---|---|
| data-vx-tabs / role="tablist" | Tab group. |
| vx-tab-link / role="tab" / aria-controls | Button and associated panel. |
| vx-tab-panel / role="tabpanel" / aria-labelledby | Panel and title. |
| aria-orientation="vertical" | Uses the up and down arrows. |
const tabs = Vexaro.getInstance('#demo-tabs');
tabs.select(1); // tab index or element
// Event: vx:tabs:change; detail.tabExpansion animates height through CSS Grid. Collapsed content is inert. Use single for one open item; multiple allows several.
JavaScript: yes
No. Use native HTML, CSS and JavaScript.
Yes. Override the tokens in your CSS.
<div class="vx-accordion vx-w-full" data-vx-accordion="single">
<h3 class="vx-accordion-heading"><button class="vx-btn vx-btn-secondary" type="button" data-vx-toggle="collapse" data-vx-target="#answer-one" aria-expanded="false" aria-controls="answer-one">Do I need a framework?</button></h3>
<div class="vx-collapse" data-vx-collapse id="answer-one"><div class="vx-collapse-inner"><p class="vx-p-4">No. Use native HTML, CSS and JavaScript.</p></div></div>
<h3 class="vx-accordion-heading"><button class="vx-btn vx-btn-secondary" type="button" data-vx-toggle="collapse" data-vx-target="#answer-two" aria-expanded="false" aria-controls="answer-two">Can I customize it?</button></h3>
<div class="vx-collapse" data-vx-collapse id="answer-two"><div class="vx-collapse-inner"><p class="vx-p-4">Yes. Override the tokens in your CSS.</p></div></div>
</div>| Class / attribute | Usage |
|---|---|
| data-vx-collapse / vx-collapse / vx-collapse-inner | Panel and required inner child. |
| data-vx-toggle="collapse" / data-vx-target | Trigger. |
| data-vx-accordion="single|multiple" | Single or multiple mode. |
| is-open | Initially open state. |
const collapse = Vexaro.getInstance('#answer-one');
collapse.open(); collapse.close(); collapse.toggle();
// Events: vx:collapse:open, vx:collapse:closeExtra information available on focus and hover. Escape dismisses it. You can hover the tooltip; essential content must remain visible on the page.
JavaScript: yes
<span class="vx-tooltip-wrap"><button class="vx-btn vx-btn-secondary" type="button" data-vx-toggle="tooltip" data-vx-target="#demo-tip">Token help</button><span class="vx-tooltip" id="demo-tip" role="tooltip" hidden>Tokens are shared CSS variables.</span></span>| Class / attribute | Usage |
|---|---|
| vx-tooltip-wrap / vx-tooltip | Positioning and content. |
| data-vx-toggle="tooltip" / data-vx-target | Trigger. |
| aria-describedby | Preserves existing descriptions and adds the tooltip. |
const tip = Vexaro.getInstance('[data-vx-toggle="tooltip"]');
tip.open(); tip.close();
// Events: vx:tooltip:open, vx:tooltip:closeExtra content opened by clicking. Escape and outside clicks close it. Focus follows the natural order; the popover is not modal.
JavaScript: yes
CSS-first, native components and documented public API.
<button class="vx-btn vx-btn-secondary" type="button" data-vx-toggle="popover" data-vx-target="#demo-popover" aria-controls="demo-popover" aria-expanded="false">About this version</button>
<div class="vx-popover" id="demo-popover" role="region" aria-labelledby="popover-title" hidden><h3 id="popover-title">VEXARO UI 1.0</h3><p>CSS-first, native components and documented public API.</p></div>| Class / attribute | Usage |
|---|---|
| vx-popover | Fixed panel repositioned in the viewport. |
| data-vx-toggle="popover" / data-vx-target | Trigger. |
| aria-controls / aria-expanded | Association and state. |
const popover = Vexaro.getInstance('[data-vx-toggle="popover"]');
popover.open(); popover.close(); popover.toggle();
// Events: vx:popover:open, vx:popover:closeNative date selection. The browser provides localization, calendar navigation and keyboard interaction. The public value uses YYYY-MM-DD; min, max and required follow HTML5.
JavaScript: yes
<div class="vx-field vx-w-full"><label class="vx-label" for="demo-date">Deadline</label><input class="vx-input" type="date" id="demo-date" data-vx-datepicker min="2026-01-01" max="2030-12-31" aria-describedby="date-help"><span class="vx-help" id="date-help">Choose a date between 2026 and 2030.</span></div>| Class / attribute | Usage |
|---|---|
| input[type="date"] / vx-input / data-vx-datepicker | Native calendar with optional API. |
| min / max / required | Native constraints. |
| value | ISO format; out-of-range values throw RangeError in the API. |
const date = Vexaro.getInstance('#demo-date');
date.value = '2026-12-01';
// date.open() must be called during a user action.
// Event: vx:datepicker:change; detail.valueText-filtered list that keeps focus in the input. Arrow keys navigate, Enter selects and Escape closes. Free text is accepted; validate required options in your application.
JavaScript: yes
<div class="vx-combobox vx-w-full" data-vx-combobox id="demo-combobox">
<label class="vx-label" for="demo-city">City</label><input class="vx-input" id="demo-city" aria-describedby="city-help">
<span class="vx-help" id="city-help">Type to filter and use arrow keys to select.</span>
<ul class="vx-combobox-list" role="listbox" aria-label="Cities" id="city-options" hidden><li role="option">São Paulo</li><li role="option">Rio de Janeiro</li><li role="option">Belo Horizonte</li><li role="option">Curitiba</li></ul>
</div>| Class / attribute | Usage |
|---|---|
| data-vx-combobox / vx-combobox | Container. |
| input / [role="listbox"] / [role="option"] | Field and text options. |
| aria-expanded / aria-activedescendant / aria-selected | Managed by the component. |
const combo = Vexaro.getInstance('#demo-combobox');
combo.open(); combo.close();
// Event: vx:combobox:change; detail.value
// In a manual instance: new Vexaro.Combobox(element, { options: ['A', 'B'] })Native range with a synchronized visible value. Arrow keys, Home and End are provided by the browser.
JavaScript: yes
<div class="vx-field vx-w-full"><label class="vx-label" for="demo-range">Volume: <output id="range-value" for="demo-range">40</output>%</label><input class="vx-range" type="range" id="demo-range" min="0" max="100" step="5" value="40" data-vx-slider data-vx-output="#range-value"></div>| Class / attribute | Usage |
|---|---|
| vx-range / input[type="range"] / data-vx-slider | Native control. |
| data-vx-output | Associated output selector. |
| min / max / step | Limits and increment. |
const slider = Vexaro.getInstance('#demo-range');
slider.value = 75;
// Event: vx:slider:change; detail.value (number)Local sorting of loaded rows. Pagination shows a range of rows and announces the current page; it makes no server requests.
JavaScript: yes
| VEXARO | 18 |
|---|---|
| Atlas | 7 |
| North | 12 |
| Center | 3 |
| South | 9 |
<div class="vx-w-full">
<div class="vx-table-responsive" role="region" aria-label="Sortable projects" tabindex="0"><table class="vx-table vx-table-striped" id="demo-data-table" data-vx-table data-vx-page-size="2" data-vx-pagination="#table-pages">
<caption>Projects — click headers to sort</caption><thead><tr><th scope="col"><button type="button" data-vx-sort="text">Project</button></th><th scope="col"><button type="button" data-vx-sort="number">Tasks</button></th></tr></thead>
<tbody><tr><th scope="row">VEXARO</th><td>18</td></tr><tr><th scope="row">Atlas</th><td>7</td></tr><tr><th scope="row">North</th><td>12</td></tr><tr><th scope="row">Center</th><td>3</td></tr><tr><th scope="row">South</th><td>9</td></tr></tbody>
</table></div>
<nav class="vx-flex vx-flex-wrap vx-items-center vx-gap-3 vx-mt-4" id="table-pages" aria-label="Project pages"><button type="button" class="vx-btn vx-btn-secondary vx-btn-sm" data-vx-page="prev">Previous</button><span data-vx-page-status role="status"></span><button type="button" class="vx-btn vx-btn-secondary vx-btn-sm" data-vx-page="next">Next</button></nav>
</div>| Class / attribute | Usage |
|---|---|
| data-vx-table / data-vx-page-size / data-vx-pagination | Table, page size and controls. |
| data-vx-sort="text|number" | Sort button inside th. |
| data-vx-value | Optional cell value for sorting formatted numbers. |
| data-vx-page="prev|next" / data-vx-page-status | Pagination controls and announcement. |
const table = Vexaro.getInstance('#demo-data-table');
table.sort(1, 'descending', 'number');
table.goTo(2);
// Events: vx:table:sort (column, direction), vx:table:page (page)Utilities are optional and loaded after components. The available scale is 0, 1, 2, 3, 4, 5, 6, 8, 10, 12 and 16, referencing --vx-space-*.
| Class / attribute | Usage |
|---|---|
| vx-d-none|block|inline|inline-block|flex|grid | Display; !important overrides the component display. |
| vx-m-* / vx-p-* / vx-gap-* | Margin, padding and gap. |
| vx-mx-* / vx-my-* / vx-px-* / vx-py-* | Inline and block axes. |
| vx-mt-* / vx-mb-* / vx-ms-* / vx-me-* / vx-pt-* / vx-pb-* | Sides; ms/me follow the writing direction. |
| vx-m-auto / vx-mx-auto / vx-ms-auto | Automatic margins. |
| vx-d-md-flex / vx-p-lg-8 / vx-gap-sm-4 | Responsive: the same sm/md/lg/xl/xxl set. |
| vx-flex-md-row / vx-flex-lg-col | Responsive direction. |
| vx-text-start|center|end / vx-text-muted / vx-w-full | Text and width. |
| vx-visible / vx-invisible / vx-sr-only | Visibility and screen-reader-only content. |
<div class="vx-d-none vx-d-md-flex vx-gap-4 vx-p-4 vx-p-lg-8">
Visible from 768px; larger padding from 992px.
</div>The ESM entry exports classes, version and lifecycle functions. The global entry exposes the same members on Vexaro. Automatic initialization runs once when the page loads; call init again for content added later.
import { init, destroy, getInstance, Modal, Toast, setTheme } from 'https://ui.vexaro.solutions/dist/vexaro.js';
const area = document.querySelector('#minha-area');
init(area); // idempotent: does not duplicate listeners or instances
const modal = getInstance('#meu-modal');
modal.open();
modal.close();
Toast.show('Saved.', { duration: 5000 });
setTheme('dark');
destroy(area); // removes listeners, timers and references before unmounting
// When mounting again: init(area)
// Manual instances require an explicit instance.destroy().| Class / attribute | Usage |
|---|---|
| init(scope = document) | Returns new instances; includes scope itself when it matches. |
| getInstance(element|selector) | Returns the auto-initialized instance or null. |
| destroy(scope = document) | Destroys registered instances within the scope. |
| instance.destroy() | Removes an instance's listeners and resources; do not reuse it after destroy. |
| setTheme("light"|"dark", element?) | Updates the theme and emits vx:theme:change. |
| vx:init:error | Markup error: detail.error and console logging. |
| vx:* events | CustomEvent with bubbles=true. Component-specific data in detail. |
Each component's API is in its own section. Only Modal should trap focus while open. The library does not inject message HTML: toast and combobox text uses textContent.
The public API is frozen at version 1.0.0 according to API.md. Documented classes, tokens and methods may only be removed or change meaning in a major release. Compatible additions use minor; fixes use patch.
In React, Vue and Svelte, import CSS once and initialize components after mounting the DOM. Call destroy before unmounting. Use getInstance for auto-initialized components; do not create a second manual instance on the same element. See framework examples.
import { init, destroy } from 'https://ui.vexaro.solutions/dist/vexaro.js';
// After mounting:
init(container);
// Before unmounting:
destroy(container);import { registerVexaroElements } from './integrations/web-components.js';
registerVexaroElements();<vx-components>
<button class="vx-btn" type="button" data-vx-toggle="toast"
data-vx-message="Integration ready.">Test</button>
</vx-components>The adapter observes connection and disconnection. For content inserted later inside the element, call element.refresh(). It is not imported by the core and requires no framework or Shadow DOM.
Use a consistent heading order, visible labels and associated descriptions. Tab and Shift+Tab navigate controls; Enter and Space activate buttons. Arrows control tabs, dropdowns, comboboxes and ranges. Escape closes modal, dropdown, tooltip and popover.
The 38 token contrast pairs are checked: text and semantic colors at least 4.5:1; borders and focus over provided backgrounds at least 3:1. Disabled states have reduced opacity. Custom colors, images and arbitrary combinations need their own assessment.
Reduced motion disables animations. Modal uses a native dialog and returns focus when closed; collapsed panels are inert. Datepicker and range use native browser semantics and keyboard behavior. The library does not automatically certify an application's WCAG compliance.
node scripts/build.mjs
npm test
node scripts/browser-check.mjs
# After installing development dependencies:
npm run test:browser
npm run test:visual:updateThe local browser test uses headless Chrome and CDP; Playwright covers Chromium and Firefox, screenshots and axe-core. Human review of screen readers, zoom and final content is still required.
See CHANGELOG.md for the history and the board for review status.