Customization
@alviere/ui components are styled through a layered customization system. There are four approaches, from broadest to most targeted:
| Approach | Scope | Use when |
|---|---|---|
| CSS custom properties (design tokens) | Global or scoped | Rebranding, theming, consistent style changes across all components |
| Theme presets | Global or scoped | Switching between pre-built palettes (dark, brand-ocean, etc.) |
CSS ::part() |
Single component element | Targeting a specific internal element that no token covers |
class prop / color props |
Single component instance | One-off overrides on Button, Spinner, or Timeline |
CSS design tokens
How tokens are applied
Design tokens are automatically injected into :root when the first SDK component loads. You do not need to import any CSS file. The full token set is available as soon as any component is present on the page.
To verify injection, inspect :root in your browser's DevTools — you'll see all --alv-* variables defined there.
Overriding tokens
Redefine any token in your own stylesheet. Because the SDK injects into :root, your overrides just need to have equal or higher specificity:
/* Apply globally */
:root {
--alv-color-primary: #0066cc;
--alv-color-primary-hover: #0052a3;
--alv-font-family-sans: "Inter", sans-serif;
}
/* Scope to a specific container */
.payment-widget {
--alv-color-primary: #7c3aed;
--alv-input-height: 2.75rem;
}
Scoped overrides cascade into all SDK components rendered inside that container — useful for embedding the SDK in a section of a larger page without affecting the rest of your styles.
CSS Layers
Token styles are injected inside @layer alviere-defaults. If your application uses @layer, your override layer must be declared after alviere-defaults to win the cascade:
@layer alviere-defaults, my-overrides;
@layer my-overrides {
:root {
--alv-color-primary: #0066cc;
}
}
If you don't use @layer, unlayered rules always win over layered ones — your plain :root overrides will apply automatically.
Pre-load configuration
To set tokens before any component renders (e.g. to avoid a flash of the default brand color), configure via window.ALVIERE_TOKENS_CONFIG before the SDK script loads:
<script>
window.ALVIERE_TOKENS_CONFIG = {
customTokens: {
'--alv-color-primary': '#0066cc',
'--alv-color-primary-hover': '#0052a3'
}
};
</script>
<script src="web-components.js"></script>
To opt out of auto-injection entirely and manage the stylesheet yourself:
<script>
window.ALVIERE_TOKENS_CONFIG = { disableAutoInject: true };
</script>
Programmatic API
import { configureTokens, ensureTokensInjected } from '@alviere/ui';
configureTokens({
customTokens: {
'--alv-color-primary': '#0066cc'
}
});
// Force injection if you disabled auto-inject
ensureTokensInjected();
Shadow DOM
If you're embedding SDK components inside your own custom element (Shadow DOM), call adoptTokensInShadowRoot to make the tokens available inside your shadow root:
import { adoptTokensInShadowRoot } from '@alviere/ui';
class MyWidget extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' });
adoptTokensInShadowRoot(shadow);
// SDK components rendered inside shadow now have access to all tokens
}
}
Theme presets
Five built-in theme presets are available:
| Preset | data-theme value |
|---|---|
| Light (default) | light or omit attribute |
| Dark | dark |
| Ocean brand | brand-ocean |
| Forest brand | brand-forest |
| Sunset brand | brand-sunset |
Apply a preset by setting the data-theme attribute on any ancestor element, including :root:
<!-- Dark mode for the entire page -->
<html data-theme="dark">
<!-- Scoped to a container -->
<div data-theme="brand-ocean">
<alviere-create-account ...></alviere-create-account>
</div>
Programmatic theming
import { ThemeManager } from '@alviere/ui';
const theme = new ThemeManager();
// Apply to document root
theme.setTheme('dark');
// Apply to a specific element
theme.setTheme('brand-ocean', document.querySelector('#payment-section'));
CSS ::part()
Three components expose named CSS parts for styling internal elements that tokens don't reach. Parts are targeted with the ::part() pseudo-element.
Button
alviere-button::part(root) { /* the <button> element */ }
alviere-button::part(icon) { /* icon container */ }
alviere-button::part(label) { /* label/text content */ }
Spinner
alviere-spinner::part(root) { /* root container */ }
/* Circular variant */
alviere-spinner::part(circle) { /* SVG circle element */ }
alviere-spinner::part(circle-path) { /* circle stroke path */ }
/* Dots variant */
alviere-spinner::part(dots) { /* dots container */ }
alviere-spinner::part(dot) { /* individual dot */ }
Timeline
alviere-timeline::part(root) { /* root container */ }
alviere-timeline::part(container) { /* steps wrapper */ }
alviere-timeline::part(step-wrapper) { /* per-step wrapper */ }
alviere-timeline::part(connector) { /* connector line between steps */ }
alviere-timeline::part(step) { /* individual step */ }
alviere-timeline::part(step-indicator) { /* step circle/badge */ }
alviere-timeline::part(step-icon) { /* icon inside the indicator */ }
alviere-timeline::part(step-ripple) { /* ripple animation element */ }
alviere-timeline::part(step-label) { /* label container */ }
alviere-timeline::part(step-text) { /* label text */ }
alviere-timeline::part(step-description) { /* optional description text */ }
CSS parts are not exposed on input components, form components, or most element components. Use CSS custom properties or scoped overrides for those.
class prop
Button, Spinner, and Timeline accept a class prop for applying additional CSS classes to their root element. Other components do not support this prop.
<Button class="my-submit-btn" variant="primary">Submit</Button>
<alviere-button class="my-submit-btn">Submit</alviere-button>
Button and Spinner also accept color, primaryColor, and backgroundColor props that apply inline CSS variable overrides to a single instance without touching the global token sheet — useful for one-off color variations.
Token reference
Colors
Primary
| Token | Default |
|---|---|
--alv-color-primary |
#227e9e |
--alv-color-primary-hover |
#1b6680 |
--alv-color-primary-active |
#145164 |
--alv-color-primary-light |
#e8f4f8 |
--alv-color-primary-light-hover |
#d1e8f0 |
--alv-color-on-primary |
#ffffff |
Success
| Token | Default |
|---|---|
--alv-color-success |
#436b1d |
--alv-color-success-light |
#d4edda |
--alv-color-success-border |
#c3e6cb |
--alv-color-success-text |
#155724 |
--alv-color-on-success |
#ffffff |
--alv-color-success-rgb |
67, 107, 29 |
Error
| Token | Default |
|---|---|
--alv-color-error |
#b3311f |
--alv-color-error-light |
#f8d7da |
--alv-color-error-border |
#f5c6cb |
--alv-color-error-text |
#721c24 |
--alv-color-on-error |
#ffffff |
--alv-color-error-rgb |
179, 49, 31 |
Warning
| Token | Default |
|---|---|
--alv-color-warning |
#ffc107 |
--alv-color-warning-light |
#fff3cd |
--alv-color-warning-border |
#ffeaa7 |
--alv-color-warning-text |
#856404 |
--alv-color-on-warning |
#000000 |
Info
| Token | Default |
|---|---|
--alv-color-info |
#175db8 |
--alv-color-info-light |
#d1ecf1 |
--alv-color-info-border |
#bee5eb |
--alv-color-info-text |
#0c5460 |
--alv-color-on-info |
#ffffff |
Gray scale
| Token | Default |
|---|---|
--alv-color-gray-50 |
#fafafa |
--alv-color-gray-100 |
#f4f4f5 |
--alv-color-gray-200 |
#e4e4e7 |
--alv-color-gray-300 |
#d4d4d8 |
--alv-color-gray-400 |
#a1a1aa |
--alv-color-gray-500 |
#71717a |
--alv-color-gray-600 |
#52525b |
--alv-color-gray-700 |
#3f3f46 |
--alv-color-gray-800 |
#27272a |
--alv-color-gray-900 |
#18181b |
Surface
| Token | Default |
|---|---|
--alv-color-surface |
#ffffff |
--alv-color-surface-secondary |
#f9fafb |
--alv-color-surface-tertiary |
#f4f4f5 |
--alv-color-surface-elevated |
#ffffff |
Text
| Token | Default |
|---|---|
--alv-color-text-primary |
#18181b |
--alv-color-text-secondary |
#52525b |
--alv-color-text-tertiary |
#71717a |
--alv-color-text-disabled |
#a1a1aa |
--alv-color-text-on-primary |
#ffffff |
--alv-color-text-on-surface |
#18181b |
Border
| Token | Default |
|---|---|
--alv-color-border |
#e4e4e7 |
--alv-color-border-hover |
#d4d4d8 |
--alv-color-border-focus |
#227e9e |
--alv-color-border-error |
#b3311f |
--alv-color-border-success |
#436b1d |
Background
| Token | Default |
|---|---|
--alv-color-background |
#ffffff |
--alv-color-background-disabled |
#f4f4f5 |
--alv-color-background-hover |
#f9fafb |
--alv-color-background-active |
#f4f4f5 |
Typography
Font families
--alv-font-family-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--alv-font-family-grotesque: "Darker Grotesque", sans-serif;
--alv-font-family-mono: "Monaco", "Menlo", "Ubuntu Mono", monospace;
Font sizes
--alv-font-size-3xs: 0.625rem; /* 10px */
--alv-font-size-2xs: 0.75rem; /* 12px */
--alv-font-size-xs: 1rem; /* 16px */
--alv-font-size-sm: 1.125rem; /* 18px */
--alv-font-size-base: 1.25rem; /* 20px */
--alv-font-size-lg: 1.5rem; /* 24px */
--alv-font-size-xl: 1.75rem; /* 28px */
Font weights
--alv-font-weight-light: 300;
--alv-font-weight-normal: 400;
--alv-font-weight-medium: 500;
--alv-font-weight-semibold: 600;
--alv-font-weight-bold: 700;
Line heights
--alv-line-height-tight: 1.25;
--alv-line-height-normal: 1.5;
--alv-line-height-relaxed: 1.75;
Spacing
--alv-spacing-xs: 0.25rem; /* 4px */
--alv-spacing-sm: 0.5rem; /* 8px */
--alv-spacing-md: 1rem; /* 16px */
--alv-spacing-lg: 1.5rem; /* 24px */
--alv-spacing-xl: 2rem; /* 32px */
--alv-spacing-2xl: 3rem; /* 48px */
--alv-spacing-3xl: 4rem; /* 64px */
Border radius
--alv-border-radius-none: 0;
--alv-border-radius-sm: 0.125rem; /* 2px */
--alv-border-radius: 0.25rem; /* 4px — default */
--alv-border-radius-md: 0.375rem; /* 6px */
--alv-border-radius-lg: 0.5rem; /* 8px */
--alv-border-radius-xl: 0.75rem; /* 12px */
--alv-border-radius-2xl: 1rem; /* 16px */
--alv-border-radius-full: 9999px;
Shadows
--alv-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--alv-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--alv-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--alv-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--alv-shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
--alv-shadow-none: none;
Transitions
--alv-transition-fast: 150ms ease-in-out;
--alv-transition-normal: 250ms ease-in-out;
--alv-transition-slow: 350ms ease-in-out;
--alv-transition-none: none;
Z-index
--alv-z-dropdown: 1000;
--alv-z-sticky: 1020;
--alv-z-fixed: 1030;
--alv-z-modal-backdrop: 1040;
--alv-z-modal: 1050;
--alv-z-popover: 1060;
--alv-z-tooltip: 1070;
Component tokens
Input fields
--alv-input-height-sm: 2.5rem;
--alv-input-height: 3.25rem;
--alv-input-height-lg: 3.75rem;
--alv-input-padding-x: 0.75rem;
--alv-input-padding-y: 0.5rem;
--alv-input-border-color: var(--alv-color-border);
--alv-input-border-hover: var(--alv-color-border-hover);
--alv-input-border-focus: var(--alv-color-border-focus);
--alv-input-bg: var(--alv-color-background);
--alv-input-bg-disabled: var(--alv-color-background-disabled);
Button
/* Primary */
--alv-button-primary-bg: var(--alv-color-primary);
--alv-button-primary-bg-hover: var(--alv-color-primary-hover);
--alv-button-primary-bg-active: var(--alv-color-primary-active);
--alv-button-primary-text: var(--alv-color-on-primary);
--alv-button-primary-border: var(--alv-color-primary);
/* Secondary */
--alv-button-secondary-bg: transparent;
--alv-button-secondary-bg-hover: var(--alv-color-primary-light);
--alv-button-secondary-bg-active: var(--alv-color-primary-light-hover);
--alv-button-secondary-text: var(--alv-color-primary);
--alv-button-secondary-border: var(--alv-color-primary);
/* Tertiary */
--alv-button-tertiary-bg: transparent;
--alv-button-tertiary-bg-hover: var(--alv-color-gray-100);
--alv-button-tertiary-bg-active: var(--alv-color-gray-200);
--alv-button-tertiary-text: var(--alv-color-text-primary);
--alv-button-tertiary-border: transparent;
/* Link */
--alv-button-link-bg: transparent;
--alv-button-link-bg-hover: transparent;
--alv-button-link-bg-active: transparent;
--alv-button-link-text: var(--alv-color-primary);
--alv-button-link-text-hover: var(--alv-color-primary-hover);
--alv-button-link-border: transparent;
Spinner
--alv-spinner-color: var(--alv-color-text-primary);
--alv-spinner-size-sm: 1rem;
--alv-spinner-size-md: 1.5rem;
--alv-spinner-size-lg: 4rem;
--alv-spinner-stroke-width: 2px;
Timeline
--alv-timeline-color: var(--alv-color-gray-600);
--alv-timeline-completed-color: var(--alv-color-success);
--alv-timeline-error-color: var(--alv-color-error);
--alv-timeline-pending-color: var(--alv-color-gray-400);
--alv-timeline-step-size-sm: 1.25rem;
--alv-timeline-step-size-md: 2rem;
--alv-timeline-step-size-lg: 2.5rem;
--alv-timeline-connector-width: 3px;
--alv-timeline-font-size: var(--alv-font-size-base);
Badge
--alv-badge-primary-bg: var(--alv-color-primary);
--alv-badge-primary-text: var(--alv-color-on-primary);
--alv-badge-secondary-bg: var(--alv-color-gray-200);
--alv-badge-secondary-text:var(--alv-color-text-primary);
--alv-badge-success-bg: var(--alv-color-success);
--alv-badge-success-text: var(--alv-color-on-success);
--alv-badge-error-bg: var(--alv-color-error);
--alv-badge-error-text: var(--alv-color-on-error);
--alv-badge-warning-bg: var(--alv-color-warning);
--alv-badge-warning-text: var(--alv-color-on-warning);
--alv-badge-info-bg: var(--alv-color-info);
--alv-badge-info-text: var(--alv-color-on-info);
List
--alv-list-bg: var(--alv-color-surface);
--alv-list-border: var(--alv-color-border);
--alv-list-item-bg: transparent;
--alv-list-item-bg-hover: var(--alv-color-background-hover);
--alv-list-item-bg-active: var(--alv-color-background-active);
--alv-list-item-text: var(--alv-color-text-primary);
--alv-list-item-text-secondary: var(--alv-color-text-secondary);
Radio
--alv-radio-border: var(--alv-color-border);
--alv-radio-border-hover: var(--alv-color-border-hover);
--alv-radio-border-focus: var(--alv-color-border-focus);
--alv-radio-bg: var(--alv-color-background);
--alv-radio-checked-bg: var(--alv-color-primary);
--alv-radio-checked-border: var(--alv-color-primary);
--alv-radio-text: var(--alv-color-text-primary);
--alv-radio-text-disabled: var(--alv-color-text-disabled);
Usage examples
Dark theme
Override the semantic tokens that matter for light/dark contrast. Place this in your global stylesheet — the SDK tokens are already scoped to :root, so your overrides just need to be present on the page.
[data-theme="dark"],
[data-theme="dark"] :root {
--alv-color-primary: #60a5fa;
--alv-color-background: #1f2937;
--alv-color-surface: #374151;
--alv-color-text-primary: #f9fafb;
--alv-color-text-secondary: #d1d5db;
--alv-color-border: #4b5563;
--alv-color-border-focus: #60a5fa;
--alv-input-bg: #1f2937;
}
Or use the built-in preset:
<body data-theme="dark">...</body>
Compact layout
:root {
--alv-spacing-md: 0.75rem;
--alv-spacing-lg: 1rem;
--alv-font-size-base: 1rem;
--alv-font-size-lg: 1.25rem;
--alv-input-height: 2.5rem;
--alv-border-radius: 0.25rem;
}
Corporate branding
:root {
--alv-color-primary: #0066cc;
--alv-color-primary-hover: #0052a3;
--alv-font-family-sans: "Inter", sans-serif;
--alv-border-radius: 0.25rem;
--alv-border-radius-lg: 0.375rem;
}

