Link the stylesheet and you have the full design system — no build step. The interactive components (lightbox, modals, dropdowns, syntax highlighting) add one dependency: jQuery 4, loaded before js/arx.js.
Arx does not bundle jQuery — pull it from a CDN as shown. Skip the scripts if you only need the CSS.
<!-- Styles -->
<link rel="stylesheet" href="path/to/arx.css">
<!-- Interactive components (optional) -->
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script src="path/to/arx.js"></script>
Add arx-dark to <html> or <body> to activate the dark theme. The bundled ArxTheme plugin handles toggling, persistence, and a system-following auto mode.
Auto mode. ArxTheme.set('auto') follows the OS prefers-color-scheme and updates live when it changes. 'light'/'dark' set an explicit choice; all three persist to localStorage.
Density. Add arx-compact to a scope (e.g. <html>) to tighten the padding of inputs, buttons, table cells and list-group items; the default ("cozy") needs no class.
RTL. Set dir="rtl" on <html> (or any subtree) and the layout mirrors — components use CSS logical properties throughout. The physical spacing/text utilities (.arx-ml-*, .arx-text-left) intentionally stay fixed.
Motion & print. Arx honours prefers-reduced-motion (animations collapse) and ships a print stylesheet (interactive chrome hidden, ink-friendly). See Keyboard & A11y.
Avoid the flash. Because the theme class is applied by JavaScript, add this tiny synchronous snippet in <head> before your stylesheet so a dark-mode visitor never sees a light flash on load.
<html class="arx-dark"> <!-- dark theme -->
<html class="arx-compact"> <!-- compact density -->
<html dir="rtl"> <!-- right-to-left -->
<html class="arx-dark arx-compact" dir="rtl"> <!-- combine -->
<!-- Any element auto-toggles light/dark: -->
<button data-arx-theme-toggle>Theme</button>
// Or drive it directly:
ArxTheme.set('auto'); // follow the OS, live
ArxTheme.set('dark'); // explicit
ArxTheme.toggle(); // flip light/dark
ArxTheme.get(); // → true if dark
ArxTheme.mode(); // → 'light' | 'dark' | 'auto'
// The saved preference is restored automatically on load.
<script>(function(){try{var m=localStorage.getItem('arx-theme');
if(m==='dark'||(m==='auto'&&matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('arx-dark');}catch(e){}})();</script>
All colour tokens are CSS custom properties defined on :root (light theme) and overridden on .arx-dark. Every component reads from these variables, so switching themes requires only a class change — no CSS recompile.
| Token | Light | Dark | Usage |
|---|---|---|---|
| --arx-bg | #ffffff | #070a0e | Page background |
| --arx-bg-secondary | #f8f9fa | #0c1017 | Secondary page bg |
| --arx-surface | #ffffff | #0c1017 | Card / panel bg |
| --arx-surface-raised | #f1f3f5 | #111820 | Elevated surface (sidebar, hover) |
| --arx-border | #dee2e6 | #1c2a3a | Default border |
| --arx-border-focus | #4a9eff | #00c8ff | Focus ring colour |
| --arx-text | #212529 | #a8bfcc | Default body text |
| --arx-text-secondary | #6c757d | #6b8899 | Subdued text |
| --arx-text-muted | #adb5bd | #3d5568 | Placeholder / metadata |
| --arx-text-inverse | #ffffff | #070a0e | Text on filled backgrounds |
| --arx-primary | #0d6efd | #00c8ff | Brand accent |
| --arx-primary-hover | #0b5ed7 | #33d4ff | Button hover state |
| --arx-primary-subtle | #cfe2ff | rgba(0,200,255,.10) | Tinted bg (badges, pills) |
| --arx-primary-text | #084298 | #070a0e | Text on subtle bg |
| --arx-success | #198754 | #00e87a | Positive / confirmed |
| --arx-danger | #dc3545 | #ff4d5e | Destructive / error |
| --arx-warning | #ffc107 | #ffb627 | Caution |
| --arx-info | #0dcaf0 | #00c8ff | Informational |
To override a token for a specific component or scope, set it directly in your CSS:
<div class="arx-bg-primary">...</div>
<div class="arx-bg-success">...</div>
<div class="arx-bg-danger">...</div>
<div class="arx-bg-warning">...</div>
<div class="arx-bg-info">...</div>
<div class="arx-bg-secondary">...</div>
<div class="arx-bg-surface">...</div>
<div class="arx-bg-raised">...</div>
<div class="arx-bg-body">...</div>
<span class="arx-text-primary">Primary</span>
<span class="arx-text-secondary">Secondary</span>
<span class="arx-text-muted">Muted</span>
<span class="arx-text-success">Success</span>
<span class="arx-text-danger">Danger</span>
<span class="arx-text-warning">Warning</span>
<span class="arx-text-info">Info</span>
<div class="arx-opacity-100">...</div>
<div class="arx-opacity-75">...</div>
<div class="arx-opacity-50">...</div>
<div class="arx-opacity-25">...</div>
<div class="arx-opacity-0">...</div>
.my-component {
--arx-primary: #7c3aed; /* purple accent in this scope */
--arx-primary-subtle: #ede9fe;
}
Gradients are tokens too. Each --arx-gradient-* is composed from the accent variables, so it follows the active theme (light / dark / custom) automatically — no extra definitions. Re-aim them all by overriding --arx-gradient-angle, or redefine any single token for a bespoke blend.
.arx-bg-gradient-*.arx-text-gradient-*.arx-border-gradient-*Variants: primary, secondary, success, info, warning, danger, plus the multi-stop brand blend (the bare .arx-bg-gradient / .arx-text-gradient / .arx-border-gradient classes). Background utilities don't set a text colour — pair them with .arx-text-* as needed.
<div class="arx-bg-gradient-primary arx-text-inverse">...</div>
<div class="arx-bg-gradient">brand blend</div>
<h1 class="arx-text-gradient">Clipped to glyphs</h1>
<span class="arx-text-gradient-success">Success</span>
<div class="arx-border-gradient arx-rounded">
Rounded corners preserved
</div>
:root {
/* re-aim every gradient at once */
--arx-gradient-angle: 90deg;
/* or ship a bespoke blend */
--arx-gradient-primary:
linear-gradient(135deg, #7c3aed, #ec4899);
}
12-column flexbox grid. Wrap columns in .arx-row inside an .arx-container. Columns are full-width by default and snap at named breakpoints.
| Breakpoint | Infix | Min-width |
|---|---|---|
| Extra small | (none) | 0 |
| Small | sm | 576px |
| Medium | md | 768px |
| Large | lg | 992px |
| Extra large | xl | 1200px |
<div class="arx-container">
<div class="arx-row">
<div class="arx-col-md-8">Main content</div>
<div class="arx-col-md-4">Sidebar</div>
</div>
</div>
<!-- Full width on mobile, thirds on md+ -->
<div class="arx-row">
<div class="arx-col-12 arx-col-md-4">One</div>
<div class="arx-col-12 arx-col-md-4">Two</div>
<div class="arx-col-12 arx-col-md-4">Three</div>
</div>
<!-- Equal-width auto columns -->
<div class="arx-row">
<div class="arx-col">Auto</div>
<div class="arx-col">Auto</div>
</div>
.arx-stack is a vertical flex column; .arx-hstack is a horizontal row with centred items. Use gap modifiers to control spacing, and .arx-vr for divider lines inside an hstack.
<!-- Horizontal stack with divider -->
<div class="arx-hstack arx-hstack-gap-3">
<button class="arx-btn arx-btn-outline">Back</button>
<div class="arx-vr"></div>
<span>Step 2 of 4</span>
<div class="arx-flex-1"></div>
<button class="arx-btn arx-btn-primary">Next</button>
</div>
<!-- Vertical stack -->
<div class="arx-stack arx-stack-gap-2">
<input class="arx-input" placeholder="First name">
<input class="arx-input" placeholder="Last name">
<button class="arx-btn arx-btn-primary">Submit</button>
</div>
A list + detail layout (inbox, chat, file manager): a fixed-width .arx-split-list and a flexible .arx-split-detail, each independently scrollable, inside .arx-split. Set the list width with --arx-split-list-width. Collapses to one column under 768px.
Resizable. Add data-arx-split-resizable for a draggable divider (the splitter plugin inserts a gutter). Bound it with data-arx-split-min/-max and persist the size with data-arx-split-key. The inbox example uses it.
- Inbox
- Sent
- Drafts
<div class="arx-split" style="--arx-split-list-width:320px">
<div class="arx-split-list">…</div>
<div class="arx-split-detail">…</div>
</div>Collapsible hierarchy (file trees, nav). Add data-arx-tree to a <ul> of .arx-tree-items; an item with a nested .arx-tree-group becomes an expandable parent (chevron). Mark a parent .arx-collapsed to start closed. Keyboard: Up/Down move, Left/Right collapse/expand, Enter/Space toggle. Fires arx:tree-toggle / arx:tree-select.
- src
- components
- button.js
- index.js
- tests
- run.mjs
<ul class="arx-tree" data-arx-tree>
<li class="arx-tree-item">
<div class="arx-tree-node">src</div>
<ul class="arx-tree-group">
<li class="arx-tree-item"><div class="arx-tree-node">index.js</div></li>
</ul>
</li>
</ul>
// ArxTree.expand('#el') · .collapse('#el')Drag-to-reorder. Add data-arx-sortable to a container of .arx-sortable-items (set ="horizontal" for a row). Add an .arx-sortable-handle inside an item to restrict the grip. Pointer-based (mouse + touch); items can move between sortable containers. Fires arx:sort with each item's data-value.
- Draft the proposal
- Review with the team
- Ship it
<ul data-arx-sortable>
<li class="arx-sortable-item" data-value="a">
<span class="arx-sortable-handle">⠿</span> Item A
</li>
</ul>
// $(el).on('arx:sort', (e, order) => { … });Long-form typography container for articles and rendered Markdown. Wrap body content in .arx-prose and child elements (headings, paragraphs, lists, blockquotes, inline code, links, images, hr) get readable defaults — no per-element classes. .arx-prose-sm for a denser scale.
Heading
Body copy with a link, inline code, and a list:
- First point
- Second point
A pull quote sets the tone.
<article class="arx-prose">
<h2>…</h2> <p>…</p> <blockquote>…</blockquote>
</article>Key/value pairs on a <dl>. Default is a two-column grid; add .arx-dl-inline for right-aligned, tabular values (order totals), .arx-dl-divided for ruled rows, and .arx-dl-total on the emphasised final pair.
- Subtotal
- $336.00
- Tax
- $27.60
- Total
- $363.60
<dl class="arx-dl arx-dl-inline">
<dt>Subtotal</dt><dd>$336.00</dd>
<dt class="arx-dl-total">Total</dt><dd class="arx-dl-total">$363.60</dd>
</dl>A profile / page banner with an avatar that overlaps the banner edge, plus a meta block and optional actions. Set a background/gradient on .arx-cover or drop in an <img class="arx-cover-img">. Sizes: .arx-cover-sm / .arx-cover-lg. It stacks cleanly on small screens.
.arx-cover-center.arx-cover-hero)Designing a dual-theme token system
<div class="arx-cover">
<img class="arx-cover-img" src="banner.jpg" alt=""> <!-- optional -->
</div>
<div class="arx-cover-body"> <!-- + .arx-cover-center -->
<span class="arx-avatar arx-avatar-xl">…</span>
<div class="arx-cover-meta">…</div>
<div class="arx-cover-actions">…</div>
</div>
<!-- Hero: content sits on the banner over a scrim -->
<div class="arx-cover arx-cover-hero">
<h3>Title</h3> <p>…</p>
</div>
Pairs naturally with a tab strip below the body — see the profile example.
A card that is a radio/checkbox option (payment method, plan). Wrap an input + content in <label class="arx-radio-card"> — the whole card is the click target and reflects the checked state via :has(). .arx-radio-card-tile stacks it vertically.
<label class="arx-radio-card">
<input type="radio" name="pay" checked> Card
</label>Semantic HTML heading tags styled automatically. Use .arx-h1–.arx-h6 utility classes when you need a heading style on a non-heading element.
Display Heading One
Section Heading
Subsection Three
Card Title Four
Larger introductory paragraph text.
Regular body text. The quick brown fox jumps over the lazy dog.
De-emphasized secondary text for supplemental context.
const framework = 'Arx';
Design is not just what it looks like — it's how it works.
<h1>Display Heading One</h1>
<h2>Section Heading</h2>
<!-- Utility classes apply heading styles to any element -->
<p class="arx-h1">Styled as h1</p>
<p class="arx-lead">Larger intro paragraph.</p>
<p class="arx-muted">De-emphasized text.</p>
<code>const x = 1;</code>
<blockquote>A thoughtful quote.</blockquote>
Styles the native <kbd> element (and .arx-kbd) as a pressable key. Nest keys to show combinations.
Press <kbd>Ctrl</kbd> + <kbd>K</kbd> to search.
A plain rule is the .arx-divider utility. .arx-divider-label adds centred text (align it with -start/-end), and .arx-divider-vertical separates inline items.
<hr class="arx-divider"> <!-- plain rule -->
<div class="arx-divider-label">OR</div>
<div class="arx-divider-label arx-divider-label-start">Section</div>
<span>A</span><span class="arx-divider-vertical"></span><span>B</span>
Image + caption wrapper. Use .arx-figure-block to make it full-width.
<figure class="arx-figure">
<img src="photo.jpg" alt="..." class="arx-figure-img">
<figcaption class="arx-figure-caption">
Fig. 1 — Description of the image.
</figcaption>
</figure>
Wrap in .arx-table-responsive for horizontal scrolling on small screens.
| # | Plugin | Status | Version |
|---|---|---|---|
| 1 | Framework (Arx) | Stable | 1.0.0 |
| 2 | Modal | In Progress | — |
| 3 | Dropdown | Planned | — |
| 4 | Tooltip | Planned | — |
| Modifier | Effect |
|---|---|
| .arx-table-bordered | Border on all cells and outer edge |
| .arx-table-striped | Alternating row backgrounds |
| .arx-table-hover | Row highlight on hover |
| .arx-table-sm | Reduced cell padding |
| .arx-row-success/danger/warning/info | Row background color |
<div class="arx-table-responsive">
<table class="arx-table arx-table-bordered arx-table-hover">
<thead>
<tr><th>Name</th><th>Status</th></tr>
</thead>
<tbody>
<tr>
<td>Framework</td>
<td><span class="arx-badge arx-badge-success">Stable</span></td>
</tr>
<tr class="arx-row-warning">
<td>Modal</td>
<td><span class="arx-badge arx-badge-warning">WIP</span></td>
</tr>
</tbody>
</table>
</div>
Add data-arx-table to an .arx-table to make it sortable, searchable and paginated — all client-side, reading the existing <tbody> rows. Mark sortable columns with data-arx-sort (optionally ="number"/"date"/"text"); clicking a header cycles asc → desc → off. Add data-arx-table-search for a filter box and data-arx-table-page-size="N" for pagination (it reuses the .arx-pagination component).
| Name | Score | Role |
|---|---|---|
| Charlie | 30 | Admin |
| Alice | 100 | Owner |
| Bob | 2 | Member |
| Dave | 45 | Member |
| Erin | 78 | Admin |
<table class="arx-table arx-table-hover" data-arx-table
data-arx-table-search data-arx-table-page-size="10">
<thead><tr>
<th data-arx-sort>Name</th>
<th data-arx-sort="number">Score</th>
<th>Role</th> <!-- not sortable -->
</tr></thead>
<tbody> … </tbody>
</table>
// $('#el').arxTable('sort', 2, 'desc') // column (1-based), dir
// $('#el').arxTable('search', 'ali') · ('page', 2) · ('refresh')
// $(el).on('arx:table-sort arx:table-page arx:table-search', …);
Switch .arx-tab-pane panels from any .arx-nav tab strip. Mark the active trigger with data-arx-tab="#paneId"; add data-arx-tabs to the strip to opt into ARIA roles and arrow-key navigation. Click handling is auto-wired by js/arx.js.
<ul class="arx-nav arx-nav-tabs" data-arx-tabs>
<li class="arx-nav-item">
<a class="arx-nav-link arx-active" data-arx-tab="#one">One</a></li>
<li class="arx-nav-item">
<a class="arx-nav-link" data-arx-tab="#two">Two</a></li>
</ul>
<div class="arx-tab-content">
<div class="arx-tab-pane arx-active" id="one">First panel</div>
<div class="arx-tab-pane" id="two">Second panel</div>
</div>
<!-- Works with arx-nav-tabs / -pills / -underline / -vertical -->
// Clicks are auto-wired. To switch programmatically:
$('[data-arx-tabs]').tabs('show', '#two');
ArxTabs.show('#two');
// React to changes:
$('[data-arx-tabs]').on('arx:tabchange', function (e, tab, pane) {
console.log('Showing', pane.id);
});
Navigation path trail. The separator is a CSS pseudo-element — change it with the --arx-breadcrumb-separator custom property or use the variant modifier classes.
<ol class="arx-breadcrumb">
<li class="arx-breadcrumb-item"><a href="#">Home</a></li>
<li class="arx-breadcrumb-item"><a href="#">Components</a></li>
<li class="arx-breadcrumb-item arx-active">Breadcrumb</li>
</ol>
<!-- Separator variants -->
<ol class="arx-breadcrumb arx-breadcrumb-chevron">...</ol> <!-- › -->
<ol class="arx-breadcrumb arx-breadcrumb-dot">...</ol> <!-- · -->
<!-- Custom separator -->
<ol class="arx-breadcrumb" style="--arx-breadcrumb-separator: '→'">...</ol>
Page navigation controls. Mark the active page with .arx-active and disabled items with .arx-disabled.
<ul class="arx-pagination">
<li><a class="arx-page-link arx-disabled" href="#">← Prev</a></li>
<li><a class="arx-page-link arx-active" href="#">1</a></li>
<li><a class="arx-page-link" href="#">2</a></li>
<li><a class="arx-page-link" href="#">Next →</a></li>
</ul>
<!-- Sizes: arx-pagination-sm, arx-pagination-lg -->
<!-- Style: arx-pagination-pills -->
Wrap label + input pairs in .arx-form-group for consistent vertical spacing. All input types share a consistent focus ring via .arx-input, .arx-textarea, and .arx-select.
| Class | Element | Description |
|---|---|---|
| .arx-form-group | div | Vertical spacing wrapper |
| .arx-label | label | Styled form label |
| .arx-input | input | Text, email, password, number |
| .arx-textarea | textarea | Multi-line input |
| .arx-select | select | Dropdown with custom arrow |
| .arx-input-sm / -lg | input/select | Size modifiers |
| .arx-is-valid / -invalid | input etc. | Validation border colors |
| .arx-valid-feedback | div | Success message below field |
| .arx-invalid-feedback | div | Error message below field |
| .arx-form-text | div | Help text below field |
| .arx-check | label | Checkbox/radio wrapper |
| .arx-check-input | input | Custom-styled toggle |
| .arx-input-group | div | Horizontal input + addon/button |
| .arx-input-addon | span | Text prefix/suffix |
| .arx-btn (in group) | button | Joins seamlessly as a leading/trailing action |
<div class="arx-form-group">
<label class="arx-label">Email</label>
<input type="email" class="arx-input" placeholder="you@example.com">
<div class="arx-form-text">We never share your email.</div>
</div>
<div class="arx-form-group">
<label class="arx-label">Role</label>
<select class="arx-select">
<option>Admin</option>
</select>
</div>
<div class="arx-form-group">
<label class="arx-label">Notes</label>
<textarea class="arx-textarea"></textarea>
</div>
<input class="arx-input arx-is-valid" value="correct@email.com">
<div class="arx-valid-feedback">Looks good!</div>
<input class="arx-input arx-is-invalid" value="bad input">
<div class="arx-invalid-feedback">Please enter a valid email.</div>
<label class="arx-check">
<input type="checkbox" class="arx-check-input">
Enable notifications
</label>
<label class="arx-check">
<input type="radio" class="arx-check-input" name="plan">
Starter plan
</label>
<div class="arx-input-group">
<span class="arx-input-addon">https://</span>
<input type="text" class="arx-input" placeholder="yourdomain.com">
</div>
<div class="arx-input-group">
<input type="text" class="arx-input" placeholder="Search...">
<button class="arx-btn arx-btn-primary">Go</button>
</div>
Additional form controls that complement the core form elements.
<label class="arx-form-switch">
<input type="checkbox" class="arx-switch-input">
Enable notifications
</label>
<!-- Sizes: arx-switch-sm, arx-switch-lg -->
<div class="arx-form-group">
<label class="arx-label">Volume</label>
<input type="range" class="arx-range" min="0" max="100" value="60">
</div>
<!-- Requires placeholder=" " (space) for :not(:placeholder-shown) state -->
<div class="arx-float">
<input type="text" class="arx-input" placeholder=" ">
<label>Full name</label>
</div>
<div class="arx-float">
<select class="arx-select">
<option>Admin</option>
</select>
<label>Role</label>
</div>
A radio-backed toggle group for one mutually-exclusive choice — the checked option lifts into a "thumb". Pure CSS: the native radios hold the state (and post with a form), so no JS is needed. Use .arx-segmented-primary for an accent thumb, -sm/-lg for size, and -block to fill the width.
<div class="arx-segmented">
<label class="arx-segment">
<input type="radio" name="view" checked><span>Day</span>
</label>
<label class="arx-segment">
<input type="radio" name="view"><span>Week</span>
</label>
</div>
<!-- Accent thumb: arx-segmented-primary -->
<!-- Sizes: arx-segmented-sm / -lg Full width: -block -->
A field that collects values as removable chips. Enter or comma adds the typed value; Backspace on an empty field removes the last chip; pasting a comma list adds several at once. Set data-arx-tags-name to keep a hidden input synced (comma-joined) for form posts. Auto-wired by js/arx.js.
<div class="arx-tag-input" data-arx-tags
data-arx-tags-name="labels"
data-arx-tags-value="design,frontend">
<input class="arx-tag-input-field" placeholder="Add a tag…">
</div>
<!-- Options: data-arx-tags-max, data-arx-tags-dupes="true" -->
<!-- Standalone chip: <span class="arx-tag arx-tag-primary"> -->
$('#el').tags('add', 'newtag');
$('#el').tags('remove', 'oldtag');
$('#el').tags('get'); // → ['design', 'frontend']
$('#el').tags('clear');
// Event: $('#el').on('arx:tagschange', (e, list) => { … });
Two flavours: .arx-file styles the native file input with no JS, and .arx-dropzone (auto-wired) adds drag-and-drop with a removable file list. The dropzone rebuilds the input's FileList via DataTransfer, so it still posts correctly.
<!-- Styled native input (no JS) -->
<input type="file" class="arx-file" multiple>
<!-- Dropzone with drag & drop -->
<div class="arx-dropzone" data-arx-dropzone>
<div class="arx-dropzone-icon">⬆</div>
<div class="arx-dropzone-text"><strong>Browse</strong> or drop</div>
<div class="arx-dropzone-hint">PNG, JPG up to 5 MB</div>
<input type="file" class="arx-dropzone-input" multiple>
</div>
// $('#dz').dropzone('files') → [File] · $('#dz').dropzone('clear')
// $('#dz').on('arx:fileschange', (e, files) => { … });
Pure-CSS star rating. .arx-rating is interactive and radio-backed (the inputs go in reverse 5→1 order so the fill works; the flex flips them visually). .arx-rating-display shows a read-only value and supports fractions via --arx-rating. Sizes -sm/-lg.
<!-- Interactive — note the reverse 5→1 order -->
<div class="arx-rating">
<input type="radio" name="r" id="r5"><label for="r5"></label>
<input type="radio" name="r" id="r4" checked><label for="r4"></label>
<!-- … r3, r2, r1 … -->
</div>
<!-- Read-only, fractional -->
<span class="arx-rating-display" style="--arx-rating:3.5"
role="img" aria-label="3.5 out of 5"></span>
A number input flanked by −/+ buttons. Auto-wired via data-arx-quantity; it reads min/max/step from the input, clamps typed values, and disables a button at its bound. Sizes -sm/-lg.
<div class="arx-quantity" data-arx-quantity>
<button class="arx-quantity-btn" data-arx-quantity-dec
aria-label="Decrease">−</button>
<input class="arx-quantity-input" type="number"
value="1" min="0" max="10" step="1">
<button class="arx-quantity-btn" data-arx-quantity-inc
aria-label="Increase">+</button>
</div>
// $('#q').quantity('get') · $('#q').quantity('set', 5)
// $('#q').on('arx:quantitychange', (e, val) => { … });
A combobox with filter-as-you-type suggestions, match highlighting, and full keyboard control (↑/↓ to move, Enter to pick, Esc to close). Auto-wired via data-arx-autocomplete; the list comes from an inline JSON <script>, or pass an array / async function to $(el).autocomplete().
<div class="arx-autocomplete" data-arx-autocomplete>
<input class="arx-input" placeholder="Search…">
<script type="application/json" class="arx-autocomplete-source">
["Apple", "Apricot", "Banana"]
</script>
</div>
// Array of strings or { label, value } objects
$('#ac').autocomplete({ source: ['Apple', 'Banana'], minChars: 1, limit: 8 });
// Async (remote) source
$('#ac').autocomplete({ source: function (q, done) {
fetch('/search?q=' + q).then(r => r.json()).then(done);
}});
// $('#ac').on('arx:autocomplete-select', (e, item) => { … });
A styled, optionally-searchable replacement for a native <select>. Add data-arx-select to the select — it stays the value holder, so forms submit normally. Add multiple for a multiselect with removable chips; the search box appears automatically past ~8 options (or force it with data-arx-select-search).
<select data-arx-select data-arx-select-search>
<option value="">Choose…</option>
<option value="apple">Apple</option>
</select>
<!-- Multiselect (chips) -->
<select multiple data-arx-select> … </select>
// $('#el').arxSelect('open'|'close'|'refresh')
// $(el).on('arx:select-change', (e, val) => { … });
A calendar popover on a text input — add data-arx-datepicker. Values are ISO YYYY-MM-DD. Keyboard: arrows move, Enter selects, Esc closes. Bound with data-arx-datepicker-min/-max; add data-arx-datepicker-range for a start–end range.
<input class="arx-input" data-arx-datepicker
placeholder="YYYY-MM-DD">
<!-- Range + bounds -->
<input class="arx-input" data-arx-datepicker
data-arx-datepicker-range
data-arx-datepicker-min="2026-01-01"
data-arx-datepicker-max="2026-12-31">
// $('#el').datepicker('open'|'close')
// $(el).on('arx:datepicker-change', (e, val) => { … });
A column popup on a text input — add data-arx-timepicker. The value is always written as 24-hour HH:MM; add data-arx-timepicker-12h for an AM/PM display. Set the minute granularity with data-arx-timepicker-step and bounds with data-arx-timepicker-min/-max (out-of-range options are disabled). Keyboard: ↑/↓ move within a column, Enter selects, Esc closes.
<input class="arx-input" data-arx-timepicker
data-arx-timepicker-step="15">
<!-- 12-hour display, business-hours bounds -->
<input class="arx-input" data-arx-timepicker
data-arx-timepicker-12h
data-arx-timepicker-min="09:00"
data-arx-timepicker-max="17:00">
// value is 24h HH:MM regardless of display
// $('#el').timepicker('open'|'close')
// $(el).on('arx:timepicker-change', (e, val) => { … });
A styled single or dual-handle slider that enhances a native <input type="range"> (kept as the form value-holder). Add data-arx-range; for a min–max selection wrap two range inputs in a data-arx-range-dual element. Drag, click the track, or use the keyboard (arrows, Home/End, PageUp/Down). Optional data-arx-range-tooltip and data-arx-range-ticks="N".
<input type="range" min="0" max="100" value="40"
data-arx-range data-arx-range-tooltip
data-arx-range-ticks="5">
<!-- Dual handle (min–max) -->
<div data-arx-range data-arx-range-dual data-arx-range-tooltip>
<input type="range" min="0" max="100" value="25">
<input type="range" min="0" max="100" value="75">
</div>
// $('#el').arxRange('get') → 40 or [25, 75]
// $('#el').arxRange('set', 60) · ('set', [20, 80])
// $(el).on('arx:range-change', (e, v) => { … });
Segmented one-time-password entry — add data-arx-otp to an empty element and it builds the cells. Typing auto-advances, Backspace steps back, arrows move, and pasting a code distributes it across cells. A hidden input (data-arx-otp-name) holds the concatenated value for form submission. Numeric by default; set data-arx-otp-type="text" to allow letters.
Fires arx:otp-change on every edit and arx:otp-complete once all cells are filled.
<div data-arx-otp data-arx-otp-length="6"
data-arx-otp-name="code"></div>
// $('#el').otp('get') → "123456"
// $('#el').otp('set', '000000') · ('clear')
// $(el).on('arx:otp-complete', (e, code) => { … });
Enhance a <input type="password"> with data-arx-password: a show/hide eye toggle (on by default), an optional 4-segment strength meter (data-arx-password-meter), and an optional satisfied/unsatisfied rules checklist (data-arx-password-rules). Scoring is dependency-free (length tiers + character-class variety).
<input type="password" class="arx-input"
data-arx-password
data-arx-password-meter
data-arx-password-rules
data-arx-password-min="8">
// $('#el').arxPassword('score') → 0–4
// $('#el').arxPassword('toggle') // show/hide
// $(el).on('arx:password-strength', (e, s) => s.score, s.label);
Format-as-you-type with data-arx-mask. Use a token pattern (9 digit, A letter, * alphanumeric; any other character is a literal) or a named preset: phone, card, date, currency. Set data-arx-mask-name to mirror the unmasked value to a hidden input for the server.
<input class="arx-input" data-arx-mask="(999) 999-9999">
<input class="arx-input" data-arx-mask="phone">
<input class="arx-input" data-arx-mask="currency"
data-arx-mask-name="amount_raw">
// $('#el').mask('get') → "(555) 123-4567"
// $('#el').mask('raw') → "5551234567"
// $(el).on('arx:mask-change', (e, v) => { … });
A saturation/value square + hue slider on a hex <input> (kept as the value holder). Add data-arx-colorpicker; a swatch opens the popover, and typing a hex updates the picker (and vice-versa). Fires native change + arx:colorchange.
<input class="arx-input" data-arx-colorpicker value="#0d6efd">
// $('#el').colorpicker('get') → "#0d6efd"
// $('#el').colorpicker('set', '#ff0000')
// $(el).on('arx:colorchange', (e, hex) => { … });Use .arx-btn as the base. Add a variant class for color. Works on <button>, <a>, and <input type="submit">.
<button class="arx-btn arx-btn-primary">Primary</button>
<button class="arx-btn arx-btn-secondary">Secondary</button>
<button class="arx-btn arx-btn-success">Success</button>
<button class="arx-btn arx-btn-danger">Danger</button>
<button class="arx-btn arx-btn-warning">Warning</button>
<button class="arx-btn arx-btn-info">Info</button>
<button class="arx-btn arx-btn-ghost">Ghost</button>
<button class="arx-btn arx-btn-outline">Outline</button>
<button class="arx-btn arx-btn-outline-primary">Outline Primary</button>
<button class="arx-btn arx-btn-primary arx-btn-sm">Small</button>
<button class="arx-btn arx-btn-primary">Default</button>
<button class="arx-btn arx-btn-primary arx-btn-lg">Large</button>
<button class="arx-btn arx-btn-primary" disabled>Disabled</button>
<button class="arx-btn arx-btn-primary arx-disabled">Disabled (class)</button>
<button class="arx-btn arx-btn-primary arx-btn-loading">Loading</button>
<div class="arx-btn-group">
<button class="arx-btn arx-btn-outline">Left</button>
<button class="arx-btn arx-btn-outline">Center</button>
<button class="arx-btn arx-btn-outline">Right</button>
</div>
Generic dismissal button. Used inside modals, alerts, offcanvas panels, and anywhere an × is needed.
Close button positioned top-right.
<button class="arx-close">✕</button>
<button class="arx-close" disabled>✕</button>
Flexible content containers with optional header and footer slots.
Full-stack developer working on the Arx Framework.
A card with only a body — no header or footer.
<div class="arx-card">
<div class="arx-card-header">Card Title</div>
<div class="arx-card-body">
<div class="arx-card-title">Heading</div>
<p class="arx-card-text">Body text goes here.</p>
<button class="arx-btn arx-btn-primary arx-btn-sm">Action</button>
</div>
<div class="arx-card-footer">Footer note</div>
</div>
Three strategies for keeping cards at equal height in a row: .arx-card-deck (gapped), .arx-card-group (flush), and .arx-row-equal with column classes.
Short body text so this card has less content.
This card has more text to demonstrate that the deck keeps all cards at the same height automatically.
Medium length body.
Lifts on hover via .arx-card-hover.
Row stretches cards to equal height with .arx-row-equal.
<div class="arx-card-deck">
<div class="arx-card">
<img class="arx-card-img-top" src="…" style="height:160px;">
<div class="arx-card-body">
<div class="arx-card-title">Title</div>
<p class="arx-card-text">…</p>
<button class="arx-btn arx-btn-primary arx-btn-sm">Action</button>
</div>
</div>
<div class="arx-card">…</div>
<div class="arx-card">…</div>
</div>
<!-- .arx-row-equal turns each .arx-col into a flex
container so the card fills the full column height -->
<div class="arx-row arx-row-equal">
<div class="arx-col-md-4">
<div class="arx-card arx-card-hover">
<div class="arx-card-body">…</div>
</div>
</div>
<div class="arx-col-md-4">…</div>
<div class="arx-col-md-4">…</div>
</div>
<div class="arx-card-group">
<div class="arx-card">…</div>
<div class="arx-card">…</div>
</div>
<!-- Image at top -->
<img class="arx-card-img-top" src="…" style="height:200px;">
<!-- Image at bottom -->
<img class="arx-card-img-bottom" src="…" style="height:200px;">
<!-- Horizontal layout (image on the left) -->
<div class="arx-card arx-card-horizontal">
<img class="arx-card-img-side" src="…">
<div class="arx-card-body">…</div>
</div>
<!-- Lift + shadow on hover -->
<div class="arx-card arx-card-hover">…</div>
Responsive image grids with hover overlays. Use .arx-gallery-masonry for a CSS-columns masonry layout. Add data-lightbox to the images and call the bundled lightbox plugin for a full-screen viewer — the images below are wired up live.
Masonry variant
<!-- 3-column square grid with hover overlay -->
<div class="arx-gallery arx-gallery-3 arx-gallery-square">
<div class="arx-gallery-item">
<img class="arx-gallery-img" src="…" alt="">
<div class="arx-gallery-overlay">
<span class="arx-gallery-caption">Caption</span>
</div>
</div>
<!-- Span 2 columns -->
<div class="arx-gallery-item arx-gallery-span-2">…</div>
</div>
<!-- Column count: arx-gallery-2/3/4/5/6 -->
<!-- Height: arx-gallery-sm/md/lg/xl -->
<!-- Aspect ratio: arx-gallery-square / wide -->
<!-- Gap: arx-gallery-gap-0/1/2/3/4 -->
<!-- CSS-columns masonry — images keep their natural height -->
<div class="arx-gallery-masonry arx-gallery-3">
<div class="arx-gallery-item">
<img class="arx-gallery-img" src="…" alt="">
</div>
<!-- more items… -->
</div>
<!-- Images sharing a data-lightbox value form one gallery -->
<div class="arx-gallery arx-gallery-3 arx-gallery-square">
<img class="arx-gallery-img" src="photo1.jpg" data-lightbox="trip" alt="Sunrise">
<img class="arx-gallery-img" src="photo2.jpg" data-lightbox="trip" alt="Harbor">
</div>
<script>
// One call wires arrows, captions, counter, keyboard nav and swipe.
$('[data-lightbox]').lightbox();
</script>
Dialog overlays driven by js/arx.js. Trigger declaratively with a data-arx-modal attribute pointing at a hidden source element — no JavaScript required. The source element's children are moved into the dialog and restored on close, so any markup works — confirmations, forms (interactive widgets keep working), long scrolling content. Add .arx-modal-sm / .arx-modal-lg to the source to resize, style actions with .arx-modal-btn-primary / .arx-modal-btn-danger, and add data-arx-modal-close to any button to dismiss. The dialog traps focus while open, returns focus to the trigger on close, hides the background from assistive tech (inert + aria-hidden), and animates with CSS transitions that honour prefers-reduced-motion. Modals stack — opening one from inside another layers it on top, and Escape closes only the topmost. Open one in code with $('#my-modal').arxModal() or ArxModal.open('#my-modal'), fire-and-await ArxModal.confirm() / ArxModal.alert() for Promise-based dialogs, or listen for arx:modal-open / arx:modal-close — the legacy data-modal / ArxModals names still work. Try the examples:
<button data-arx-modal="#my-modal">Launch modal</button>
<!-- Hidden source — children are moved into the dialog -->
<div id="my-modal" style="display:none;">
<div class="arx-modal-header"><h3>Deploy to production?</h3></div>
<div class="arx-modal-body"><p>Publishes the build to all regions.</p></div>
<div class="arx-modal-footer">
<button class="arx-modal-btn" data-arx-modal-close>Cancel</button>
<button class="arx-modal-btn arx-modal-btn-primary" data-arx-modal-close>Deploy</button>
</div>
</div>
<!-- Size goes on the template root: arx-modal-sm / arx-modal-lg -->
<div id="confirm" class="arx-modal-sm" style="display:none;">
<div class="arx-modal-body"><p>Delete this project?</p></div>
<div class="arx-modal-footer">
<button class="arx-modal-btn" data-arx-modal-close>Cancel</button>
<!-- Destructive action -->
<button class="arx-modal-btn arx-modal-btn-danger" data-arx-modal-close>Delete</button>
</div>
</div>
<!-- Any markup works in the body — e.g. a form -->
<div id="signin" style="display:none;">
<div class="arx-modal-header"><h3>Sign in</h3></div>
<div class="arx-modal-body">
<div class="arx-form-group">
<label class="arx-label">Email</label>
<input class="arx-input" type="email">
</div>
</div>
<div class="arx-modal-footer">
<button class="arx-modal-btn arx-modal-btn-primary" data-arx-modal-close>Sign in</button>
</div>
</div>
// data-arx-modal triggers work automatically. To drive modals from code:
ArxModal.open('#my-modal');
ArxModal.close(); // closes the top modal
$('#my-modal').arxModal(); // open
$('#my-modal').arxModal('close'); // close this one
// Lifecycle events (fired on the source element)
$('#my-modal').on('arx:modal-open', () => { /* … */ });
$('#my-modal').on('arx:modal-close', () => { /* … */ });
// Legacy names still work: data-modal, ArxModals, $.fn.modal
// confirm() / alert() generate the dialog — no hidden markup needed.
const ok = await ArxModal.confirm({
title: 'Delete project?',
body: 'This cannot be undone.',
confirmText: 'Delete',
danger: true // red confirm button; also: size: 'sm' | 'lg'
});
if (ok) deleteProject(); // resolves true on confirm, false on cancel/Esc
await ArxModal.alert({ title: 'Saved', body: 'Your changes are live.' });
// Modals stack: opening one from inside another layers it on top —
// the lower dialog is dimmed, inert, and Esc closes only the top.
A panel that slides in from any screen edge over a backdrop — for menus, filters, and detail views. Author the panel inline; a [data-arx-offcanvas="#id"] trigger toggles it. The drawer traps focus, restores it to the trigger on close, locks body scroll, and closes on Esc or backdrop click. Edge: -start (default), -end, -top, -bottom.
<button data-arx-offcanvas="#menu">Menu</button>
<aside id="menu" class="arx-offcanvas arx-offcanvas-start" tabindex="-1">
<div class="arx-offcanvas-header">
<h3 class="arx-offcanvas-title">Navigation</h3>
<button class="arx-close" data-arx-offcanvas-close
aria-label="Close"></button>
</div>
<div class="arx-offcanvas-body"> … </div>
</aside>
data-arx-offcanvas="#id" Trigger — toggles the panel
data-arx-offcanvas-close Closes the panel (on any element inside)
data-arx-backdrop="false" Open without a backdrop
data-arx-scroll="true" Don't lock body scroll while open
Sizes: arx-offcanvas-sm / -lg Edges: -start -end -top -bottom
$('#menu').offcanvas('show'); // or 'hide', 'toggle'
ArxOffcanvas.open('#menu'); ArxOffcanvas.close();
Highlights the nav link for the section currently in view. Add data-arx-scrollspy to any nav whose links point at section ids (href="#id"); the matching link gets .arx-active as you scroll. Works with .arx-nav, .arx-sidenav, or plain links. This very page's sidebar is a scrollspy.
Set data-arx-scrollspy-offset to clear a fixed navbar, and data-arx-scrollspy-root to spy inside a scrollable container instead of the page.
<nav class="arx-sidenav" data-arx-scrollspy
data-arx-scrollspy-offset="80">
<a class="arx-sidenav-link" href="#intro">Intro</a>
<a class="arx-sidenav-link" href="#usage">Usage</a>
</nav>
<section id="intro"> … </section>
<section id="usage"> … </section>
<!-- Spy a scroll container: data-arx-scrollspy-root="#box" -->
<!-- Recompute after DOM changes: ArxScrollspy.refresh() -->
Lightweight code highlighting from js/arx.js. Call .syntax() on any pre code; the language is auto-detected from a class, data-lang, or the content itself. Pass { lineNumbers: true } for a gutter — a copy button is added automatically.
function debounce(fn, wait) {
let t;
return function (...args) {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, args), wait);
};
}
const log = debounce(msg => console.log(msg), 200);
log('hello'); // fires once, 200ms after the last call
<pre><code class="language-javascript">…your code…</code></pre>
<script>
// Highlight one block, with line numbers + a copy button
$('#docs-syntax-demo code').syntax({ lineNumbers: true, copyButton: true });
// …or every code block on the page
$('pre code').syntax();
</script>
Contextual feedback messages. Add .arx-alert-dismissible and an .arx-alert-close button to make alerts closeable — dismissal is auto-wired by js/arx.js (it fades out and removes the alert on click; no handler required).
<div class="arx-alert arx-alert-info">
<span class="arx-alert-icon">ℹ</span>
<div class="arx-alert-body">
<div class="arx-alert-title">Information</div>
Your account has been updated.
</div>
</div>
<!-- Variants: arx-alert-info/success/warning/danger -->
<!-- Dismissible — close is auto-wired by js/arx.js -->
<div class="arx-alert arx-alert-danger arx-alert-dismissible">
<div class="arx-alert-body">Authentication failed.</div>
<button class="arx-alert-close" aria-label="Dismiss">✕</button>
</div>
// Auto-wired: clicking .arx-alert-close (or any [data-arx-dismiss]
// inside an alert) fades it out and removes it. To dismiss in code:
$('#my-alert').alert('close');
ArxAlert.dismiss('#my-alert');
A full-width strip for announcements, promos, or cookie notices. Solid by default, or .arx-banner-subtle for a tint; status variants success/danger/warning/info. A .arx-banner-close (or [data-arx-dismiss]) slides it away — auto-wired.
<div class="arx-banner">
<div class="arx-banner-body">
Notice — <a href="#">learn more</a>.
</div>
<button class="arx-banner-close" data-arx-dismiss
aria-label="Dismiss">×</button>
</div>
<!-- Variants: arx-banner-subtle, -success, -danger, -warning, -info -->
Small inline labels for status, counts, and metadata. Three style tiers — subtle (default), solid, and outline — plus dot indicators and positioned notification counters.
Subtle (default)
Solid
Outline
Sizes & Shapes
Dot Indicators
Positioned (notification counter)
In-context Usage
New Dashboard Beta
Releases v1.1.0
<span class="arx-badge arx-badge-primary">Primary</span>
<span class="arx-badge arx-badge-success">Success</span>
<span class="arx-badge arx-badge-danger">Danger</span>
<span class="arx-badge arx-badge-warning">Warning</span>
<span class="arx-badge arx-badge-info">Info</span>
<span class="arx-badge arx-badge-secondary">Secondary</span>
<span class="arx-badge arx-badge-solid-primary">Primary</span>
<span class="arx-badge arx-badge-solid-success">Success</span>
<span class="arx-badge arx-badge-solid-danger">Danger</span>
<span class="arx-badge arx-badge-outline-primary">Primary</span>
<span class="arx-badge arx-badge-outline-success">Success</span>
<span class="arx-badge arx-badge-outline-danger">Danger</span>
<span class="arx-badge arx-badge-primary arx-badge-sm">Small</span>
<span class="arx-badge arx-badge-primary">Default</span>
<span class="arx-badge arx-badge-primary arx-badge-lg">Large</span>
<!-- Square (non-pill) shape -->
<span class="arx-badge arx-badge-solid-danger arx-badge-square">12</span>
<!-- Static dot -->
<span class="arx-badge arx-badge-dot arx-badge-solid-success"></span>
<!-- Animated ping for live status -->
<span class="arx-badge arx-badge-dot arx-badge-solid-success arx-badge-pulse"></span>
<div class="arx-badge-wrap">
<button class="arx-btn arx-btn-outline">Notifications</button>
<span class="arx-badge arx-badge-solid-danger arx-badge-pos">9</span>
</div>
<!-- Badge inline in a heading -->
<h4>Dashboard <span class="arx-badge arx-badge-success arx-badge-sm">Beta</span></h4>
<!-- Badge inside a button -->
<button class="arx-btn arx-btn-outline">
Inbox <span class="arx-badge arx-badge-solid-danger">3</span>
</button>
Circular user image or initials. Size with .arx-avatar-xs … -xl, colour initials with a variant (.arx-avatar-primary, etc.), add a corner .arx-avatar-status dot, square it with .arx-avatar-square, or overlap several in an .arx-avatar-group.
<!-- Initials -->
<span class="arx-avatar arx-avatar-success">JL</span>
<!-- Image -->
<span class="arx-avatar"><img src="me.jpg" alt="Me"></span>
<!-- With a status dot -->
<span class="arx-avatar">SP
<span class="arx-avatar-status arx-avatar-status-online"></span>
</span>
<!-- Stacked group -->
<span class="arx-avatar-group">
<span class="arx-avatar arx-avatar-sm">A</span>
<span class="arx-avatar arx-avatar-sm arx-avatar-info">B</span>
</span>
<!-- Sizes: -xs -sm (default) -lg -xl · Shape: -square
Status: -online -busy -away · Variants: -primary…-info, -muted -->
A metric block — mono .arx-stat-label, large .arx-stat-value, and an optional .arx-stat-trend (add -up / -down for colour). Drops straight into a card body for dashboards.
<div class="arx-card"><div class="arx-card-body">
<div class="arx-stat">
<span class="arx-stat-label">Revenue</span>
<span class="arx-stat-value">$48.2k</span>
<span class="arx-stat-trend arx-stat-trend-up">▲ 12.4%</span>
</div>
</div></div>
<!-- Value sizes: .arx-stat-value-sm / -lg -->
Message bubbles for conversation UIs. Wrap a series in .arx-chat; mark own messages with .arx-bubble-sent (accent, right-aligned) and replies with .arx-bubble-received.
<div class="arx-chat">
<div class="arx-bubble arx-bubble-received">Hello!</div>
<div class="arx-bubble arx-bubble-sent">Hi there 👋</div>
</div>
Flexible component for displaying a series of content. Add .arx-list-group-action to make rows clickable.
<ul class="arx-list-group">
<li class="arx-list-group-item">Profile</li>
<li class="arx-list-group-item">Settings</li>
</ul>
<!-- Clickable rows -->
<ul class="arx-list-group">
<a class="arx-list-group-item arx-list-group-action arx-active" href="#">Active</a>
<a class="arx-list-group-item arx-list-group-action" href="#">Item</a>
</ul>
<!-- Variants: arx-list-group-flush, arx-list-group-numbered, arx-list-group-horizontal -->
<!-- Item colors: arx-list-group-success/danger/warning/info/primary -->
A vertical sequence of events on a rail — for activity feeds and histories. Colour a node's dot with .arx-timeline-{primary…info} on the item.
-
09:24Deployed v1.2.0All checks green; rolled out to production.
-
08:50Merged pull request #214Add toast & popover components.
-
YesterdayOpened the milestone
<ul class="arx-timeline">
<li class="arx-timeline-item arx-timeline-success">
<div class="arx-timeline-time">09:24</div>
<div class="arx-timeline-title">Deployed v1.2.0</div>
<div class="arx-timeline-body">All checks green.</div>
</li>
</ul>
<!-- Dot colour: arx-timeline-primary/success/danger/warning/info -->
A centred placeholder for "no data / no results" zones — icon, headline, supporting text, and an optional action. Sizes -sm/-lg. It sizes to its content by default; add .arx-empty-fill to stretch it to a card body or grid column's height (content stays centred).
When someone writes to you, it'll show up right here.
<div class="arx-empty">
<div class="arx-empty-icon">📭</div>
<div class="arx-empty-title">No messages yet</div>
<p class="arx-empty-text">When someone writes, it shows here.</p>
<div class="arx-empty-action">
<button class="arx-btn arx-btn-primary">Compose</button>
</div>
</div>
A numbered progress indicator. Steps auto-number via a CSS counter; mark progress with .arx-step-complete and .arx-step-active. The visual is pure CSS — add data-arx-stepper and the JS drives it as a wizard (next/prev buttons, optional panels, .arx-stepper-clickable steps). Add .arx-stepper-vertical for a vertical rail.
- Account
- Profile
- Review
<ol class="arx-stepper" data-arx-stepper
data-arx-step-panels="#panes">
<li class="arx-step"><span class="arx-step-marker"></span>
<span class="arx-step-label">Account</span></li>
<li class="arx-step"> … </li>
</ol>
<div id="panes">
<div class="arx-step-pane"> … </div> <!-- one per step -->
</div>
<button data-arx-step-prev="#…">Back</button>
<button data-arx-step-next="#…">Next</button>
<!-- Mark steps directly (no JS) -->
<li class="arx-step arx-step-complete"> … </li>
<li class="arx-step arx-step-active"> … </li>
$('#wizard').stepper('next'); // or 'prev'
$('#wizard').stepper('goto', 2);
ArxStepper.goTo('#wizard', 0);
// Event: $('#wizard').on('arx:stepchange', (e, i) => { … });
Collapsible panels built on the native <details> element — works with no JavaScript. Add the open attribute to expand by default, or .arx-accordion-flush for a borderless variant. The optional JS plugin adds single-open (exclusive) mode and a slide animation — opt in with data-arx-accordion="single" or $('.arx-accordion').accordion().
What is Arx?
Is it responsive?
Does it need JavaScript?
Single-open (animated) — only one panel stays open at a time:
Shipping
Returns
Warranty
<div class="arx-accordion">
<details class="arx-accordion-item" open>
<summary class="arx-accordion-header">Title</summary>
<div class="arx-accordion-body">Content…</div>
</details>
<details class="arx-accordion-item">
<summary class="arx-accordion-header">Another</summary>
<div class="arx-accordion-body">More content…</div>
</details>
</div>
<!-- Borderless: add .arx-accordion-flush -->
<!-- Single-open + animated: add data-arx-accordion="single" -->
// Auto-wired when data-arx-accordion is present. Or init manually:
$('.arx-accordion').accordion({
single: true, // close others when one opens
animate: true, // slide the body open/closed
duration: 220
});
Click toggling is auto-wired by js/arx.js — it toggles .arx-open on the .arx-dropdown and closes on outside-click or Escape. Use .arx-dropdown-end to right-align the menu.
<div class="arx-dropdown">
<button class="arx-btn arx-dropdown-toggle">Menu</button>
<div class="arx-dropdown-menu">
<span class="arx-dropdown-header">Account</span>
<a class="arx-dropdown-item arx-active" href="#">Profile</a>
<a class="arx-dropdown-item" href="#">Settings</a>
<div class="arx-dropdown-divider"></div>
<a class="arx-dropdown-item" href="#">Sign out</a>
</div>
</div>
// Auto-wired by js/arx.js — clicking a .arx-dropdown-toggle
// toggles .arx-open, outside-click and Escape close it.
// To close programmatically:
ArxDropdown.closeAll();
Pure-CSS tooltips via a data-arx-tooltip attribute — they appear on hover and keyboard focus. Position with .arx-tooltip-top (default), -bottom, -left, or -right.
<button class="arx-btn" data-arx-tooltip="Saves your changes">
Save
</button>
<!-- Position: arx-tooltip-top (default) / bottom / left / right -->
<button class="arx-btn arx-tooltip-bottom" data-arx-tooltip="Below">
Bottom
</button>
The richer companion to the tooltip — a click- or hover-triggered overlay with an optional title and rich body. Popovers are appended to <body> and positioned by JS, so they escape overflow:hidden ancestors and flip to the opposite side when there isn't room. Add data-arx-popover="…" with the body text.
<!-- Text body (default: click trigger, top placement) -->
<button class="arx-btn" data-arx-popover="Body text here"
data-arx-popover-title="Title">Info</button>
<!-- Hover trigger, placed to the right -->
<button class="arx-btn" data-arx-popover="Quick hint"
data-arx-trigger="hover" data-arx-placement="right">?</button>
<!-- Rich content cloned from a hidden element -->
<a data-arx-popover-target="#tpl" data-arx-popover-title="Details">Open</a>
<div id="tpl" hidden><strong>Any</strong> markup works.</div>
data-arx-popover Body text (escaped)
data-arx-popover-target Selector of a hidden element to clone as the body
data-arx-popover-title Optional header text
data-arx-placement top (default) | bottom | left | right
data-arx-trigger click (default) | hover
// Programmatic control
$('#el').popover('show'); // or 'hide', 'toggle', 'dispose'
ArxPopover.closeAll();
Add data-arx-validate to a <form> and it validates from the native HTML constraints you already write — required, type="email", pattern, min/max, minlength/maxlength — driving the .arx-is-invalid border and an auto-injected .arx-invalid-feedback message. Fields validate on blur and on submit; once invalid they re-check as you type. Submit is blocked and the first invalid field is focused.
| Attribute | On | Description |
|---|---|---|
| data-arx-validate | form | Enables validation for the form |
| data-arx-validate-trigger | form | blur (default) · input (eager) · submit |
| data-arx-validate-focus | form | Set off to skip focusing the first invalid field |
| data-arx-rules | field | Pipe-delimited custom rules, e.g. required|matches:#pw |
| data-arx-msg-<rule> | field | Override the message for one rule (e.g. data-arx-msg-required) |
| data-arx-msg | field | Single message for any failure on the field |
| data-arx-validate-skip | field | Exclude the field from validation |
<form data-arx-validate>
<div class="arx-form-group">
<input class="arx-input" type="email" required
data-arx-msg-required="We need your email.">
</div>
<div class="arx-form-group">
<input class="arx-input" type="password" id="pw"
required minlength="8">
</div>
<div class="arx-form-group">
<input class="arx-input" type="password"
data-arx-rules="matches:#pw"
data-arx-msg-matches="Passwords don't match.">
</div>
<button class="arx-btn arx-btn-primary">Create</button>
</form>
// Register a named rule — return true | false | a message
// | a Promise of any of those (async checks gate submit).
ArxValidate.addRule('username', function (value) {
return fetch('/api/check?u=' + value)
.then(r => r.json())
.then(d => d.free || 'That username is taken.');
}, 'Choose a username.');
// Programmatic control
$('#signup').arxValidate(); // → true if every field passes
$('#signup').arxValidate('field', '#email');
$('#signup').arxValidate('reset'); // clear classes + messages
// Events fired on the form
$('#signup').on('arx:validate-success', () => save());
$('#signup').on('arx:validate-invalid', (e, p) => p.first.focus());
A slide track with arrow controls, indicator dots (auto-filled), keyboard arrows, autoplay (pauses on hover/focus), and touch swipe. Auto-wired via data-arx-carousel; set data-arx-carousel-autoplay="ms" and data-arx-carousel-loop="false" as needed.
<div class="arx-carousel" data-arx-carousel
data-arx-carousel-autoplay="4000">
<div class="arx-carousel-track">
<div class="arx-carousel-slide"><img src="1.jpg"></div>
<div class="arx-carousel-slide"><img src="2.jpg"></div>
</div>
<button class="arx-carousel-prev" aria-label="Previous"></button>
<button class="arx-carousel-next" aria-label="Next"></button>
<div class="arx-carousel-indicators"></div>
</div>
// $('#c').carousel('next' | 'prev') · ('goto', 2)
// $('#c').on('arx:slide', (e, i) => { … });
Set the width of .arx-progress inline to reflect the current value. Wrap in .arx-progress-labeled to show a percentage label alongside.
<div class="arx-progress-wrap">
<div class="arx-progress" style="width:60%"></div>
</div>
<!-- Variants: arx-progress-success/danger/warning/info -->
<!-- Animated: arx-progress-striped -->
<!-- Sizes on the wrap: arx-progress-sm, arx-progress-lg -->
<!-- With label -->
<div class="arx-progress-labeled">
<div class="arx-progress-wrap">
<div class="arx-progress arx-progress-success" style="width:88%"></div>
</div>
<span class="arx-progress-label">88%</span>
</div>
Three variants: border circle, bouncing dots, and grow pulse. All are inline elements — nest inside a button or centre in a container.
<!-- Circle spinner -->
<div class="arx-spinner"></div>
<div class="arx-spinner arx-spinner-sm"></div>
<div class="arx-spinner arx-spinner-lg"></div>
<!-- Color variants -->
<div class="arx-spinner arx-spinner-success"></div>
<div class="arx-spinner arx-spinner-danger"></div>
<!-- Dots -->
<div class="arx-spinner-dots"><span></span><span></span><span></span></div>
<!-- Grow pulse -->
<div class="arx-spinner-grow"></div>
<!-- Inside a button -->
<button class="arx-btn arx-btn-primary arx-d-flex arx-align-center arx-gap-2">
<div class="arx-spinner arx-spinner-sm"></div>
Loading…
</button>
Skeleton loading states for content that hasn't loaded yet. Use .arx-placeholder-glow on a parent for a shimmer sweep instead of the default pulse.
<!-- Pulse (default) -->
<div class="arx-placeholder arx-placeholder-lg arx-w-75"></div>
<div class="arx-placeholder arx-w-100"></div>
<div class="arx-placeholder arx-placeholder-btn"></div>
<!-- Shimmer sweep -->
<div class="arx-placeholder-glow">
<div class="arx-placeholder arx-w-100"></div>
<div class="arx-placeholder arx-w-75"></div>
</div>
<!-- Sizes: arx-placeholder-xs/sm/lg/xl -->
<!-- Widths: arx-w-25/50/75 or inline width -->
<!-- Button shape: arx-placeholder-btn -->
<!-- Card wrapper: arx-skeleton -->
Transient, stacked notifications fired from JS. ArxToast creates a container per screen corner on demand — you don't write toast markup by hand. Toasts auto-dismiss after duration ms (default 4000; 0 stays until closed) and pause while hovered or focused.
ArxToast.show('Profile saved');
ArxToast.success('Saved', { title: 'Done', duration: 4000 });
ArxToast.danger('Upload failed', { duration: 0 }); // sticky
// Options: title, variant, duration (0 = sticky), position,
// dismissible, icon (string/HTML or false), progress
// Variants: ArxToast.primary/success/danger/warning/info
// Positions: top-right (default), top-left, bottom-right,
// bottom-left, top-center, bottom-center
ArxToast.clear(); // dismiss all
<button data-arx-toast="Copied to clipboard"
data-arx-toast-variant="success"
data-arx-toast-title="Done">Copy</button>
Margin and padding utilities follow the pattern .arx-{property}{side}-{scale}. Scale runs 0–5.
| Pattern | Property | Side | Scale → value |
|---|---|---|---|
arx-m-{n} | margin | all | 0→0, 1→0.25rem, 2→0.5rem, 3→1rem, 4→1.5rem, 5→3rem |
arx-p-{n} | padding | all | |
arx-mt/mb/ml/mr-{n} | margin | top/bottom/left/right | — |
arx-pt/pb-{n} | padding | top/bottom | — |
arx-mx-{n} / arx-my-{n} | margin | x / y axis | — |
arx-px-{n} / arx-py-{n} | padding | x / y axis | — |
arx-mx-auto | margin | x-axis | auto — centers block elements |
arx-d-none | arx-d-block | arx-d-inline | arx-d-flex
arx-d-inline-flex | arx-d-inline-block | arx-d-grid
arx-flex-row | arx-flex-col | arx-flex-wrap | arx-flex-nowrap
arx-flex-1 | arx-flex-auto | arx-flex-none
arx-gap-1 ... arx-gap-5
arx-justify-start | arx-justify-center | arx-justify-end
arx-justify-between | arx-justify-around
arx-align-start | arx-align-center | arx-align-end | arx-align-stretch
arx-text-primary
arx-text-secondary
arx-text-muted
arx-text-success
arx-text-danger
arx-text-warning
arx-fw-bold
arx-fw-light
arx-text-uppercase
arx-text-left | arx-text-center | arx-text-right
arx-text-primary | arx-text-secondary | arx-text-muted
arx-text-success | arx-text-danger | arx-text-warning | arx-text-info
arx-text-uppercase | arx-text-lowercase | arx-text-nowrap | arx-text-truncate
arx-fw-light | arx-fw-normal | arx-fw-medium | arx-fw-bold
arx-w-25 | arx-w-50 | arx-w-75 | arx-w-100 | arx-w-auto
arx-h-25 | arx-h-50 | arx-h-75 | arx-h-100 | arx-h-auto
arx-min-vh-100 /* min-height: 100vh */
arx-vh-100 /* height: 100vh */
/* Position */
arx-position-relative | arx-position-absolute | arx-position-fixed | arx-position-sticky
/* Overflow */
arx-overflow-hidden | arx-overflow-auto | arx-overflow-scroll
/* Visibility */
arx-visible | arx-invisible
/* Borders */
arx-border | arx-border-0 | arx-border-top | arx-border-bottom
arx-rounded | arx-rounded-sm | arx-rounded-lg | arx-rounded-pill | arx-rounded-0
/* Shadow */
arx-shadow-none | arx-shadow-sm | arx-shadow | arx-shadow-lg
/* Screen-reader only */
arx-sr-only
/* Divider */
<hr class="arx-divider">
Animated count-up for stats. Add data-arx-counter="N" — it counts up when scrolled into view. Options: -from, -duration, -decimals, -prefix, -suffix (thousands separators are added automatically). Honours prefers-reduced-motion (jumps to the final value). Fires arx:countend.
<span data-arx-counter="1284">0</span>
<span data-arx-counter="48.2" data-arx-counter-prefix="$"
data-arx-counter-suffix="k" data-arx-counter-decimals="1">0</span>
// ArxCounter.start('#el') · $(el).on('arx:countend', …)A thin scroll-progress bar pinned to the top of the viewport. Add a .arx-reading-progress element with data-arx-reading-progress — its width tracks whole-page scroll. Set the attribute to a selector (e.g. ="#article") to track progress through that element instead.
Used on the article example (scroll it to see the bar fill).
<div class="arx-reading-progress" data-arx-reading-progress></div>
<!-- or track a specific element -->
<div class="arx-reading-progress" data-arx-reading-progress="#article"></div>Fade or slide elements in as they enter the viewport. Add data-arx-reveal — the value is the direction: fade (default), up, down, left, right, zoom. Stagger with data-arx-reveal-delay (ms), tune with -duration and -threshold, and set -once="false" to re-animate on every entry. Content stays visible if JS never loads, and reduced-motion users see it immediately (never hidden).
Scroll the page so these enter the viewport to see them animate.
<div data-arx-reveal>fades in</div>
<div data-arx-reveal="up" data-arx-reveal-delay="120">…</div>
<div data-arx-reveal="zoom" data-arx-reveal-duration="800">…</div>
// Directions: fade | up | down | left | right | zoom
// ArxReveal.refresh() — re-scan after injecting DOM
// $(el).on('arx:reveal', …)Counts down to a target date or time. Add data-arx-countdown with an ISO target (a bare date is read as local midnight). For the segmented display, nest [data-arx-countdown-part] boxes; otherwise the element's text becomes a compact 12d 03:04:05. Fires arx:countdown-end at zero; set data-arx-countdown-expired for the finished text.
<!-- compact text -->
<span data-arx-countdown="2027-01-01T00:00"
data-arx-countdown-expired="Live!"></span>
<!-- segmented -->
<div class="arx-countdown" data-arx-countdown="2027-01-01">
<div class="arx-countdown-segment" data-arx-countdown-part="days">
<span class="arx-countdown-value">00</span>
<span class="arx-countdown-label">Days</span></div>
<!-- hours, minutes, seconds … -->
</div>
// $('#el').countdown('start'|'stop') · arx:countdown-endA ⌘K / Ctrl-K overlay that fuzzy-filters a list of actions. Mark the overlay data-arx-command; items are .arx-command-item with an .arx-command-label (and optional icon + .arx-command-hint). Arrow keys move, Enter activates (an item with an href navigates; otherwise fires arx:command-select), Esc closes.
- Go to DocumentationG D
- Toggle dark modeT
- Open Playground
- Browse examples
<div class="arx-command" data-arx-command>
<div class="arx-command-panel">
<input class="arx-command-input">
<ul class="arx-command-list">
<li class="arx-command-item" data-arx-command-action="docs">
<span class="arx-command-label">Documentation</span></li>
</ul>
</div>
</div>
// ⌘K / Ctrl-K · ArxCommand.open() · arx:command-select [action]A month event grid (distinct from the date picker). Add data-arx-calendar; events come from an inline JSON <script class="arx-calendar-events"> as [{ date, title, variant }]. Prev/next/today navigate; clicking a day fires arx:calendar-date and an event fires arx:calendar-event.
<div data-arx-calendar>
<script type="application/json" class="arx-calendar-events">
[{"date":"2026-06-12","title":"Release","variant":"success"}]
</script>
</div>
// $('#el').calendar('next'|'prev'|'today')
// $(el).on('arx:calendar-date', (e, iso) => { … });Every interactive component is operable from the keyboard and exposes ARIA state. Arx also honours prefers-reduced-motion (animations collapse) and ships a print stylesheet (chrome hidden, ink-friendly).
| Component | Keys |
|---|---|
| Dropdown | ↓ open · ↑↓ move · Home/End · Esc close |
| Select | ↓/Enter open · type to filter · ↑↓ · Enter choose · Esc close |
| Date picker | ←↑↓→ move day · PgUp/PgDn month · Enter select · Esc close |
| OTP / PIN | type to advance · Backspace back · ←→ move · paste fills all |
| Range slider | ←→ ±step · Home/End · PgUp/PgDn ±10 |
| Data table | Enter/Space on a column header to sort |
| Accordion | Enter/Space toggle a section (native <details>) |
| Command palette | ⌘/Ctrl+K open · type to filter · ↑↓ move · Enter run · Esc close |
| Tree | ↑↓ move · ←/→ collapse/expand · Enter toggle |
| Context menu | right-click opens · ↑↓ move · Enter activate · Esc close |
| Modal / Off-canvas | Esc to close |
Global conventions
- Tab / Shift+Tab move focus; visible focus rings throughout.
- Active scrollspy links and stepper steps set
aria-current. - Copy buttons announce success to screen readers (
aria-live). - Auto theme:
ArxTheme.set('auto')follows the OS and tracks live changes.
Arx ships a local copy of Lucide (v1.17.0) — a clean, consistent open-source icon set with 1000+ SVG icons. It is bundled under the ISC License.
| Attribute / Property | Effect |
|---|---|
| data-lucide="name" | Selects the icon by its kebab-case name (e.g. arrow-right, check-circle) |
| style="width:Xpx;height:Xpx;" | Controls icon size — defaults to 24 × 24 px |
| style="color:…" | Stroke colour — icons inherit currentColor by default |
| stroke-width | Pass via data-stroke-width="1.5" or override in CSS with stroke-width |
Browse the full icon set at lucide.dev/icons. Lucide is © Lucide Icons and Contributors, released under the ISC License.
<script src="vendor/lucide/lucide.min.js"></script>
<!-- Place i tags wherever you need icons -->
<i data-lucide="heart"></i>
<i data-lucide="star"></i>
<i data-lucide="user"></i>
<!-- Call once after the DOM is ready -->
<script>
lucide.createIcons();
</script>
<!-- Inside a button -->
<button class="arx-btn arx-btn-primary arx-d-flex arx-align-center arx-gap-2">
<i data-lucide="plus" style="width:16px;height:16px;"></i>
New item
</button>
<!-- Colour follows CSS color -->
<i data-lucide="heart" style="width:20px;height:20px;color:var(--arx-danger);"></i>
<i data-lucide="check-circle" style="width:20px;height:20px;color:var(--arx-success);"></i>
<!-- Size via width/height -->
<i data-lucide="star" style="width:32px;height:32px;"></i>