Components

UI gallery

Responsive grid with demos and code samples. Built to hop back to the start page easily.

Back to projects

Design systems

Interactive components

Grid overview of reusable UI patterns with short code explanations.

Back to projects

Smooth toggle

Interactive

Animated toggle with clear focus state, voiced labels, and ARIA support.

Uses aria-pressed to announce the state.

<button aria-pressed="false" class="toggle">
  <span class="sr-only">Toggle notifications</span>
  <span class="knob">On</span>
  <span class="label">On</span>
</button>

Slider with live value

Feedback

Slider updates the value in real time with aria-valuetext and aria-live.

<label for="volume">Choose level</label>
<input id="volume" type="range" aria-label="Choose level" />
<span aria-live="polite">45%</span>

Gradient CTA

Visual

Triggers an animated gradient background with strong contrast and explanatory text.

The gradient is WCAG AA tested against text and activates with the button below.

<div class="cta" data-gradient-card>...
  <button data-gradient-toggle>Activate</button>
</div>

Accordion

Structure

Expandable panel that toggles height and updates aria-expanded.

<button aria-expanded="false" aria-controls="panel">Question</button>
<div id="panel" hidden>Answer</div>

Modal / lightbox

Overlay

Opens via button, closes with overlay, Escape, or button. Focus is trapped inside the dialog.

Backdrop uses role="dialog" and aria-modal="true".

<button data-open-modal>Show modal</button>
<div role="dialog" aria-modal="true" hidden>Content</div>

Tabs with keyboard

Navigation

Arrow keys move focus between tabs; panels are controlled with aria-controls.

Automated regression tests delivered 12% more features without bug reports.
<div role="tablist">
  <button role="tab" aria-selected="true" aria-controls="panel-1">Tab 1</button>
  <button role="tab" aria-selected="false" aria-controls="panel-2">Tab 2</button>
</div>
<div id="panel-1" role="tabpanel">Content 1</div>
<div id="panel-2" role="tabpanel" hidden>Content 2</div>

Form with validation

Form

Form shows validation feedback and stops submission until inputs are correct.

<form novalidate>
  <label for="email">Email
    <input type="email" id="email" required />
  </label>
  <label for="role">Role
    <select id="role" required>...</select>
  </label>
  <button type="submit">Validate</button>
</form>

Copy code block

Productivity

Copy button grabs code content and announces success for the user.

API client

fetch("/api/data")
  .then((res) => res.json())
  .then(console.log);

<button data-copy-code data-code-target="snippet">Copy</button>
<pre id="snippet"><code>// Your code here</code></pre>
<p data-copy-status></p>

Contact form

WCAG tested

Preview only: nothing is saved or sent. Use Contact me to reach out for real.

This is a demo form to show validation and focus behavior.

<form id="contact-form" novalidate>
  <label for="name">Name
    <input id="name" required />
  </label>
  <label for="email">Email
    <input type="email" id="email" required />
  </label>
  <label for="message">Message
    <textarea id="message" required></textarea>
  </label>
  <button type="submit">Send</button>
</form>

Live local time

Live

Real-time clock using the Intl.DateTimeFormat API with auto-refresh every second.

Current local time

Updates every second with a manual refresh option.

Local time

--:--:--

Waiting for the next tick…

How it works

The clock uses the Intl.DateTimeFormat API to read your device time and re-render it without a page reload.

const formatTime = (date) =>
  new Intl.DateTimeFormat([], {
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    timeZoneName: 'short',
  }).format(date);

setInterval(() => {
  clock.textContent = formatTime(new Date());
}, 1000);