Skip to content

Lustro component catalog ​

The full UI catalog in Lustro's visual language. Every family includes usage, live previews, implementation markup and keyboard notes. All actions use fictitious data and local state; no backend is called.

For the 52-section Bootstrap reference and its variants, see the expanded component set. The original interactive guide remains available.

Language select ​

lustro / UI library

Every component, in context.

43 families · Live previews · Usage · Markup · Keyboard notes

Buttons

Primary is for one leading action per surface. Secondary is for peer actions. Destructive uses semantic red, never the brand gradient.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-actions"><button class="l-btn l-primary" @click="showToast('Primary action selected')">Primary action</button><button class="l-btn l-secondary" @click="showToast('Secondary action selected')">Secondary</button><button class="l-btn l-quiet" @click="showToast('Quiet action selected')">Quiet</button><button class="l-btn l-danger" @click="openNative('modal',$event)">Delete...</button><button class="l-btn l-secondary" disabled>Disabled</button></div>

Button group

Button group chooses one mode; the adjacent dropdown holds secondary actions.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-actions"><div class="l-segment" aria-label="Display mode"><button v-for="item in ['Design','Code','Preview']" :key="item" :aria-pressed="choice===item" :class="{'is-active':choice===item}" @click="choice=item">{{ item }}</button></div></div>

Pagination

Lustro foundation ↗

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-actions"><a href="/lustro">Lustro foundation ↗</a><nav class="l-pages" aria-label="Example pages"><button @click="page=Math.max(1,page-1)" :disabled="page===1">Previous</button><span>Page {{page}} / 3</span><button @click="page=Math.min(3,page+1)" :disabled="page===3">Next</button></nav></div>

Alerts

Alerts occupy the page and stay until dismissed. Notifications need a clear source, severity and next action. Do not use color alone to explain the state.

Saved

Your changes are up to date.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><div v-if="alertOpen" class="l-alert l-alert-warn" role="alert"><div><strong>Needs attention</strong><p>Two checks are waiting for a review.</p></div><button class="l-icon-btn" aria-label="Dismiss notification" @click="alertOpen=false">×</button></div><button v-else class="l-btn l-secondary" @click="alertOpen=true">Show notification again</button><div class="l-alert l-alert-success"><div><strong>Saved</strong><p>Your changes are up to date.</p></div></div></div>

Toasts

A toast confirms a short-lived result; it must never be the only place for an error that needs fixing. Trigger several in quick succession: each one stays visible independently until dismissed or its six-second timer ends. Color, a label and a message communicate the variant together.

For production, keep independent IDs and timers so a new arrival never overwrites an earlier one. Render the stack in a portal above app content, cap its height and let it scroll on small screens. Keep errors that need user action inline too; the toast is only a short announcement. Respect reduced-motion when animating entry and exit.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-actions"><button class="l-btn l-primary" @click="showToast('Changes saved', 'success')">Show toast</button><button class="l-btn l-secondary" @click="showToast('Secondary action queued', 'info')">Show secondary toast</button><button class="l-btn l-secondary" @click="showToast('Review before continuing', 'warn')">Show warning</button><button class="l-btn l-secondary" @click="showToast('Could not save changes', 'error')">Show error</button></div>
<div class="l-toast-stack" aria-label="Toast notifications"><div v-for="item in toasts" :key="item.id" class="l-toast" :class="'l-toast-' + item.type" :role="item.type === 'error' ? 'alert' : 'status'"><span class="l-dot" :class="'l-dot-' + item.type" aria-hidden="true"></span><span class="l-toast-copy"><strong>{{ { success: 'Success', info: 'Info', warn: 'Warning', error: 'Error' }[item.type] }}</strong><span>{{ item.message }}</span></span><button class="l-icon-btn" :aria-label="'Dismiss ' + item.type + ' toast: ' + item.message" @click="dismissToast(item.id)">×</button></div></div>
<p>For production, keep independent IDs and timers so a new arrival never overwrites an earlier one. Render the stack in a portal above app content, cap its height and let it scroll on small screens. Keep errors that need user action inline too; the toast is only a short announcement. Respect reduced-motion when animating entry and exit.</p>

Badge

DraftReadyRunningBlocked

Use a short label with text, not a color alone. The grouped demo below includes four states.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-actions"><span class="l-badge">Draft</span><span class="l-badge l-badge-success">Ready</span><span class="l-badge l-badge-warn">Running</span><span class="l-badge l-badge-error">Blocked</span></div>
<p>Use a short label with text, not a color alone. The grouped demo below includes four states.</p>

Progress

68%

Progress has a value and a max; use an indeterminate loader only when no useful estimate exists.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><label class="l-field-label" for="sample-progress">Upload progress · 68%</label><progress id="sample-progress" max="100" value="68">68%</progress></div>
<p>Progress has a value and a max; use an indeterminate loader only when no useful estimate exists.</p>

Spinners/Loaders

Name the loading region for assistive technology.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><span class="l-spinner" role="status" aria-label="Loading"></span></div>
<p>Name the loading region for assistive technology.</p>

Placeholders

Loading summary...

Skeletons reserve space while content loads; do not leave them in a permanent empty state.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><div class="l-skeleton" aria-hidden="true"></div><p>Loading summary...</p></div>
<p>Skeletons reserve space while content loads; do not leave them in a permanent empty state.</p>

Card

Use a compact summary with a clear title, metadata and the next action; keep card links and nested buttons separate.

PROJECT · REPO A

Review changes

The latest request is waiting for your review.

Needs review
TASK · 024

Ready to ship

Checks completed. The next step is a merge.

Ready

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-grid"><article class="l-card"><span class="l-eyebrow">PROJECT · REPO A</span><h3>Review changes</h3><p>The latest request is waiting for your review.</p><div class="l-card-foot"><span class="l-badge l-badge-warn">Needs review</span><button class="l-btn l-secondary" @click="showToast('Card action selected')">Open</button></div></article><article class="l-card"><span class="l-eyebrow">TASK · 024</span><h3>Ready to ship</h3><p>Checks completed. The next step is a merge.</p><div class="l-card-foot"><span class="l-badge l-badge-success">Ready</span><button class="l-btn l-secondary" @click="showToast('Card action selected')">Details</button></div></article></div>

Accordion

Use the shared color, type and spacing tokens.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><div v-for="item in ['Tokens','Behavior','Accessibility']" :key="item" class="l-accordion"><button :aria-expanded="accordion===item" @click="accordion=accordion===item?'':item">{{item}} <span>{{accordion===item?'−':'+'}}</span></button><p v-if="accordion===item">{{item==='Tokens'?'Use the shared color, type and spacing tokens.':item==='Behavior'?'Keep transitions short and tied to a state change.':'Use labels, visible focus and reduced-motion fallbacks.'}}</p></div></div>

List group

  • 01 · Surface tokensDocs
  • 02 · Component statesReady

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><ul class="l-list"><li><span>01 · Surface tokens</span><span class="l-badge">Docs</span></li><li><span>02 · Component states</span><span class="l-badge l-badge-success">Ready</span></li></ul></div>

Tables

ComponentStatusOwner
ButtonsReadyDesign
NotificationsReviewProduct
GraphsExampleMotion
No results

Try a different search or clear your filters.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><div class="l-table-scroll"><table class="l-table"><thead><tr><th scope="col">Component</th><th scope="col">Status</th><th scope="col">Owner</th></tr></thead><tbody><tr><td>Buttons</td><td><span class="l-badge l-badge-success">Ready</span></td><td>Design</td></tr><tr><td>Notifications</td><td><span class="l-badge l-badge-warn">Review</span></td><td>Product</td></tr><tr><td>Graphs</td><td><span class="l-badge">Example</span></td><td>Motion</td></tr></tbody></table></div><div class="l-empty"><strong>No results</strong><p>Try a different search or clear your filters.</p><button class="l-btn l-secondary" @click="search=''">Clear filters</button></div></div>

Forms

Use descriptive labels, not only placeholders.
Density

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-form"><label class="l-field-label" for="sample-search">Search components</label><input id="sample-search" type="search" v-model="search" placeholder="Search by name"><small>{{search?`Searching for “${search}”`:'Use descriptive labels, not only placeholders.'}}</small><label class="l-field-label" for="sample-select">Category</label><select id="sample-select"><option>All components</option><option>Actions</option><option>Feedback</option><option>Content</option></select><label class="l-field-label" for="sample-notes">Notes</label><textarea id="sample-notes" rows="2" placeholder="Optional notes"></textarea><label class="l-check"><input type="checkbox" checked> Send updates</label><fieldset><legend>Density</legend><label class="l-check"><input type="radio" name="density" checked> Comfortable</label><label class="l-check"><input type="radio" name="density"> Compact</label></fieldset><label class="l-switch"><input type="checkbox" checked><span aria-hidden="true"></span> Enable notifications</label></div>

Tooltips

Optional help appears on hover and keyboard focus. Required instructions stay visible.

Changes apply to this workspace.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><span class="kit-tooltip"><button class="l-btn l-secondary" aria-describedby="kit-tip">Focus or hover for help</button><span id="kit-tip" role="tooltip">Changes apply to this workspace.</span></span></div>

Popovers

The adjacent disclosure stays open for longer text.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-actions"><details class="l-dropdown"><summary>More information</summary><div class="l-menu l-popover">This content stays visible until you close the panel.</div></details></div>

Close button

Use an accessible name and a hit area large enough to tap. The toast is an illustration, not a destructive action.

Close a dismissible panel

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-actions"><button class="l-close" aria-label="Close sample panel" @click="showToast('Sample panel closed')">×</button><span>Close a dismissible panel</span></div>

Collapse

For one block of secondary detail, prefer native disclosure. Accordion above coordinates several blocks.

Show delivery details

Open with Enter or Space without losing context.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><details class="l-collapse"><summary>Show delivery details</summary><p>Open with Enter or Space without losing context.</p></details></div>

Offcanvas

Drawer / sheet / offcanvas: contextual details beside the current view. Native modal behavior includes Escape and focus containment.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><button class="l-btn l-secondary" @click="openNative('sheet',$event)">Open side panel</button></div>

Scrollspy

Select a section to bring it into view; the active state is also exposed as text.

Inicio

Section inicio selected.

This is a section-selection preview, not automatic scroll tracking. Production scrollspy should use IntersectionObserver to update aria-current.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><nav class="l-spy-nav" aria-label="Example sections"><button v-for="item in ['Home','Details','Summary']" :key="item" :aria-current="menuSection===item?'location':undefined" @click="menuSection=item">{{item}}</button></nav><div class="l-card" aria-live="polite"><strong>{{menuSection}}</strong><p>Section {{menuSection.toLowerCase()}} selected.</p></div></div>
<p>This is a section-selection preview, not automatic scroll tracking. Production scrollspy should use IntersectionObserver to update aria-current.</p>

Notifications

A persistent inbox for events that need attention, distinct from short-lived toasts.

Notifications2 unread
  • Unread · Dana requested a project review4 minutes ago
  • Unread · Budget approved1 hour ago

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><div class="l-actions"><strong>Notifications</strong><span class="l-badge">{{notificationsRead?0:2}} unread</span><button class="l-btn l-secondary" @click="notificationsRead=!notificationsRead">{{notificationsRead?'Reset unread':'Mark all read'}}</button></div><ul class="l-list"><li><span>{{notificationsRead?'Read':'Unread'}} · Dana requested a project review</span><small>4 minutes ago</small></li><li><span>{{notificationsRead?'Read':'Unread'}} · Budget approved</span><small>1 hour ago</small></li></ul></div>

Attachment and file upload

Native file selection, filename and size. This preview never uploads a file.

No file selectedLocal selection only. Production: validate type and size on the server.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-form"><label>Attach a PDF or text file<input type="file" accept=".pdf,.txt" @change="selectFile"></label><span class="l-badge">{{pickedFile}}</span><small>Local selection only. Production: validate type and size on the server.</small></div>

Date picker

Use locale-aware native date input for a single date; see Calendar for richer scheduling.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-form"><label>Due date<input type="date" value="2026-10-15"></label><label>Date and time<input type="datetime-local"></label></div>

Choice card

A larger radio target for options with descriptions. Arrow keys move between native radios.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-grid"><label class="l-card"><input type="radio" name="delivery" checked> <strong>Weekly summary</strong><p>A weekly overview of your workspace.</p></label><label class="l-card"><input type="radio" name="delivery"> <strong>Daily summary</strong><p>Updates at the end of each day.</p></label></div>

Stepper

Show a finite workflow. Production steps must not bypass required validation.

  1. 1. Details
  2. 2. Review
  3. 3. Done

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><ol class="l-actions"><li v-for="(s,i) in ['Details','Review','Done']" :aria-current="step===i+1?'step':undefined" :class="{'l-badge':step===i+1}">{{i+1}}. {{s}}</li></ol><div class="l-actions"><button class="l-btn l-secondary" :disabled="step===1" @click="step--">Back</button><button class="l-btn l-secondary" :disabled="step===3" @click="step++">Continue</button></div></div>

Hover card

Optional rich context. Native disclosure also makes the information available without a mouse.

Dana Lee · Coach details
Dana Lee

Lead coach · 12 classes this month

Available

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><details><summary>Dana Lee · Coach details</summary><article class="l-card"><strong>Dana Lee</strong><p>Lead coach · 12 classes this month</p><span class="l-badge l-badge-success">Available</span></article></details></div>

Message

Keep sender and time with the content. Incoming, outgoing and system states have different roles.

Dana · 10:42

Ready for review.

You · 10:43

I will check the task list.

System · Review requested

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo"><article class="kit-bubble"><strong>Dana · 10:42</strong><p>Ready for review.</p></article><article class="kit-bubble outgoing"><strong>You · 10:43</strong><p>I will check the task list.</p></article><small>System · Review requested</small></div>

Range slider

For approximate numeric settings; always show the current value. Arrow keys adjust the native slider.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-form"><label>Volume<input type="range" min="0" max="100" v-model="volume"><output>{{volume}}%</output></label></div>

Input group

Combine a field with units or a related action while retaining a visible label.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-form"><label>Budget (USD)<div class="l-actions"><span>$</span><input type="number" min="0" value="1200"><button class="l-btn l-secondary" @click="showToast('Demo budget updated','success')">Apply</button></div></label></div>

Validation and field states

Validate after interaction, preserve entered values, and connect errors to the field.

Use a short unique name

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-form"><form @submit.prevent="formSent=true" novalidate><label>Project name<input v-model="formName" required :aria-invalid="formSent&&!formName.trim()" aria-describedby="kit-validation"></label><small id="kit-validation" role="status">{{formSent?(formName.trim()?'Demo saved':'Enter a project name'):'Use a short unique name'}}</small><label>Read-only reference<input value="PRJ-042" readonly></label><label>Disabled field<input value="Managed by workspace" disabled></label><button class="l-btn l-secondary" type="submit">Validate demo</button></form></div>

Error and retry

Explain the failure, keep context, and offer recovery. This retry is local simulation.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo" :role="failed?'alert':'status'"><strong>{{failed?'Could not load projects':'Demo projects loaded'}}</strong><p>{{failed?'Your previous changes are safe. Try again.':'Connection restored (simulation).'}}</p><button class="l-btn l-secondary" @click="failed=!failed">{{failed?'Try again':'Reset error'}}</button></div>

Empty state

Explain why there is no content and offer one useful next action.

No projects yet

Create a project to start tracking work.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-empty"><strong>No projects yet</strong><p>Create a project to start tracking work.</p><button class="l-btn l-secondary" @click="showToast('Demo project created','success')">Create demo project</button></div>

Select and suggestions

Use select for a fixed set. Datalist is browser-native suggestions, not a full custom combobox.

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-form"><label>Priority<select><option>Normal</option><option>High</option><option>Low</option></select></label><label>Owner suggestions<input list="kit-owners" placeholder="Type a name"></label><datalist id="kit-owners"><option value="Dana Lee"/><option value="Marcos Silva"/></datalist></div>

Checkbox, radio and switch

Choose many values with checkboxes, one with radios, and immediate on/off settings with a switch.

Plan

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-form"><label><input type="checkbox" checked> Email updates</label><label><input type="checkbox" disabled> Managed option (disabled)</label><fieldset><legend>Plan</legend><label><input type="radio" name="kit-plan" checked> Personal</label><label><input type="radio" name="kit-plan"> Team</label></fieldset><label class="l-switch"><input type="checkbox" role="switch" checked><span aria-hidden="true"></span> Automatic reminders</label></div>

Avatar

Initials fallback and text status. Noninteractive avatars do not need a tab stop.

DLMSAvailable

Keyboard: Tab to interactive controls. Enter activates buttons and links; Space toggles native choices. Disabled controls cannot be activated. Use Escape to close modal surfaces.

Copy implementation markup
<div class="l-demo l-actions"><span class="l-avatar" role="img" aria-label="Dana Lee">DL</span><span class="l-avatar" role="img" aria-label="Marcos Silva">MS</span><span>Available</span></div>

Archive project?

This is a local demo. No project is changed.

Project details

Dana Lee · Launch checklist

Production checklist ​

Keep labels, validation, focus, loading and disabled states. Test keyboard and screen-reader behavior. Replace local demo logic with validated application actions. Markup is Vue and uses the shared ComponentCatalog source, not an installed UI package.