A brief, auto-dismissing message
A toast slides in, confirms an action, and disappears. Click the button to trigger one.
HTML
<button id="save">Save & show toast</button> <div id="toast" class="toast">Saved!</div>
CSS
.toast {
position: fixed; bottom: 24px; right: 24px;
transform: translateY(120%); opacity: 0;
transition: transform .3s, opacity .3s;
}
.toast.show { transform: translateY(0); opacity: 1; }
JavaScript
const save = document.getElementById('save');
const toast = document.getElementById('toast');
save.addEventListener('click', () => {
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 2500);
});
Reveal a short menu
A trigger that shows a small menu on hover or keyboard focus. Pure CSS with :focus-within for basic keyboard support.
HTML
<div class="dropdown"> <button class="trigger">Options ▾</button> <div class="menu"><a>Edit</a><a>Duplicate</a><a>Delete</a></div> </div>
CSS
.dropdown { position: relative; }
.dropdown .menu { position: absolute; top: 100%; left: 0; display: none; }
.dropdown:hover .menu,
.dropdown:focus-within .menu { display: block; }
Slide a panel in from the right
A drawer slides in from a screen edge for filters, a cart, or settings. This pure-CSS version toggles with a hidden checkbox. Click “Open”.
HTML
<input type="checkbox" id="dr"> <label class="open" for="dr">Open ›</label> <nav class="drawer"> <label class="close" for="dr">×</label> <a href="#">Filters</a> <a href="#">Sort</a> <a href="#">Saved</a> </nav> <div class="body">Page content…</div>
CSS
.drawer {
position: fixed; top: 0; right: 0; height: 100%;
transform: translateX(100%); /* off-screen right */
transition: transform .3s ease;
}
#dr:checked ~ .drawer { transform: translateX(0); }
Slide in from the left
The same technique mirrored to the left edge — typical for a mobile navigation drawer. Click “Menu”.
HTML
<input type="checkbox" id="oc"> <label class="open" for="oc">☰ Menu</label> <nav class="offcanvas"> <label class="close" for="oc">×</label> <a href="#">Home</a> <a href="#">Shop</a> <a href="#">About</a> </nav> <div class="body">Page content…</div>
CSS
.offcanvas {
position: fixed; top: 0; left: 0; height: 100%;
transform: translateX(-100%); /* off-screen left */
transition: transform .3s ease;
}
#oc:checked ~ .offcanvas { transform: translateX(0); }
Switch between steps
A multi-step panel where clicking a step shows its content — here driven entirely by radio inputs, no JavaScript. Click the step tabs.
HTML
<div class="wizard"> <input type="radio" name="w" id="w1" checked> <input type="radio" name="w" id="w2"> <input type="radio" name="w" id="w3"> <div class="steps"><label for="w1">1. Account</label><label for="w2">2. Plan</label><label for="w3">3. Done</label></div> <div class="pane p1">Create your account — email and password.</div> <div class="pane p2">Choose a plan that fits your needs.</div> <div class="pane p3">All set! Review and finish.</div> </div>
CSS
.pane { display: none; }
#w1:checked ~ .p1,
#w2:checked ~ .p2,
#w3:checked ~ .p3 { display: block; } /* show the active step */
Hint what to search
A list of popular or recent queries shown under the search box to guide the user before they finish typing.
- flexbox basics
- flex vs grid
- flex-wrap examples
HTML
<input type="search" aria-label="Search" placeholder="Search…" value="flex"> <ul class="suggestions"> <li>flexbox basics</li> <li>flex vs grid</li> <li>flex-wrap examples</li> </ul>
CSS
.suggestions { border: 1px solid #e5e7eb; border-radius: 8px; }
.suggestions li { padding: 9px 12px; cursor: pointer; }
.suggestions li:hover { background: #f1f5f9; }
Filter the list as you type
Like suggestions, but the list narrows to match what's typed. Type in the box — a few lines of JavaScript do the filtering.
- Canada
- Chile
- China
- Colombia
- Croatia
HTML
<input id="country" placeholder="Type a country…"> <ul id="list"><li>Canada</li><li>Chile</li><li>China</li><li>Colombia</li><li>Croatia</li></ul>
CSS
#list li.hidden { display: none; } /* JS adds this class */
JavaScript
const input = document.getElementById('country');
const items = document.querySelectorAll('#list li');
input.addEventListener('input', () => {
const q = input.value.toLowerCase();
items.forEach(li =>
li.classList.toggle('hidden', !li.textContent.toLowerCase().startsWith(q)));
});
Collapsible panels, one open at a time
An accordion stacks expandable panels — click a header to open it and the others close. Click the headers below. The chevron rotates to show open/closed state.
A set of stacked headers that each reveal a panel of content when clicked — great for FAQs and long forms where you want to save space.
When content is naturally chunked and users only need one piece at a time — settings, FAQs, product details.
Yes — use real <button> headers and toggle aria-expanded so screen readers announce the open/closed state.
HTML
<div class="accordion">
<div class="item open">
<button class="head" aria-expanded="true">What is an accordion?<span>▾</span></button>
<div class="panel"><p>A set of stacked headers that each reveal a panel of content when clicked — great for FAQs and long forms where you want to save space.</p></div>
</div>
<div class="item">
<button class="head" aria-expanded="false">When should I use one?<span>▾</span></button>
<div class="panel"><p>When content is naturally chunked and users only need one piece at a time — settings, FAQs, product details.</p></div>
</div>
<div class="item">
<button class="head" aria-expanded="false">Is it accessible?<span>▾</span></button>
<div class="panel"><p>Yes — use real <button> headers and toggle aria-expanded so screen readers announce the open/closed state.</p></div>
</div>
</div>
CSS — animate with max-height
.panel { max-height: 0; overflow: hidden; transition: max-height .25s; }
.item.open .panel { max-height: 220px; } /* reveal */
.item.open .head span { transform: rotate(180deg); } /* flip chevron */
JavaScript — one open at a time
const heads = document.querySelectorAll('.accordion .head');
const items = document.querySelectorAll('.accordion .item');
heads.forEach(btn => btn.addEventListener('click', () => {
const item = btn.parentElement;
const isOpen = item.classList.contains('open');
items.forEach(i => i.classList.remove('open')); // close all
if (!isOpen) item.classList.add('open'); // open the clicked one
btn.setAttribute('aria-expanded', !isOpen);
}));
<details><summary> element works too — see the FAQ pattern.Keep building
See Tabs, Modal, and Microinteractions — or browse the full Component Library.