Day 16: UI Components: Buttons, Cards, Navbar, Modal
✅ Today you will learn
- Think in components with variants and sizes
- Buttons, badges, avatars, alerts
- Cards, breadcrumbs and pagination
- CSS-only tabs and a styled accordion
- A dialog modal with backdrop and a toast notification
🏢 In the real world
In a real company you rarely style one page – you build a component library that every page reuses: one button style, one card, one alert. When the designer changes the button radius, it changes everywhere. This day gives you a mini design system you can copy into every client project, including your school ERP or billing app screens.
Buttons: one base, many variants
<style>
:root { --brand: #264de4; --brand-600: #1e40af; --ok: #15803d; --warn: #b45309; --err: #b91c1c; --ink: #0f172a; --muted: #64748b; --line: #e2e8f0; --r: 10px; }
* { box-sizing: border-box; }
.btn { display: inline-flex; align-items: center; gap: .4em; font: 600 .95rem system-ui; padding: .6em 1.1em; border-radius: var(--r); border: 1.5px solid transparent; cursor: pointer; text-decoration: none; transition: background-color .2s, transform .1s; margin: 4px; }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 3px solid #f59e0b; outline-offset: 2px; }
.btn--primary { background: var(--brand); color: #fff; }
.btn--primary:hover { background: var(--brand-600); }
.btn--outline { border-color: var(--brand); color: var(--brand); background: #fff; }
.btn--ghost { color: var(--brand); background: transparent; }
.btn--danger { background: var(--err); color: #fff; }
.btn--sm { font-size: .8rem; }
.btn--lg { font-size: 1.1rem; padding: .75em 1.4em; }
.btn[disabled] { opacity: .5; pointer-events: none; }
</style>
<button class="btn btn--primary">Primary</button>
<button class="btn btn--outline">Outline</button>
<button class="btn btn--ghost">Ghost</button>
<button class="btn btn--danger">🗑 Delete</button><br>
<button class="btn btn--primary btn--sm">Small</button>
<button class="btn btn--primary btn--lg">Large</button>
<button class="btn btn--primary" disabled>Disabled</button>Badges, avatars and alerts
<style>
:root { --brand: #264de4; --brand-600: #1e40af; --ok: #15803d; --warn: #b45309; --err: #b91c1c; --ink: #0f172a; --muted: #64748b; --line: #e2e8f0; --r: 10px; }
* { box-sizing: border-box; }
.badge { display: inline-block; padding: 2px 10px; border-radius: 99px; font: 700 .72rem system-ui; letter-spacing: .02em; margin: 2px; }
.badge--ok { background: #dcfce7; color: #166534; } .badge--warn { background: #fef3c7; color: #92400e; } .badge--err { background: #fee2e2; color: #991b1b; }
.avatars { display: flex; margin: 12px 0; }
.avatar { width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; color: #fff; font: 700 14px system-ui; border: 3px solid #fff; margin-left: -10px; }
.avatar:first-child { margin-left: 0; }
.alert { display: flex; gap: 10px; padding: 12px 14px; border-radius: var(--r); border: 1px solid; margin: 8px 0; }
.alert--info { background: #eff6ff; border-color: #bfdbfe; color: #1e3a8a; }
.alert--err { background: #fef2f2; border-color: #fecaca; color: #7f1d1d; }
</style>
<span class="badge badge--ok">Paid</span><span class="badge badge--warn">Pending</span><span class="badge badge--err">Overdue</span>
<div class="avatars"><span class="avatar" style="background:#264de4">AG</span><span class="avatar" style="background:#0891b2">RM</span><span class="avatar" style="background:#c2410c">SK</span><span class="avatar" style="background:#64748b">+5</span></div>
<div class="alert alert--info" role="status">ℹ️ <div><b>Fee reminder</b><br>Term 2 fee is due on 10 November.</div></div>
<div class="alert alert--err" role="alert">⚠️ <div><b>Payment failed</b><br>Your UPI request expired. Try again.</div></div>Breadcrumbs and pagination
<style>
:root { --brand: #264de4; --brand-600: #1e40af; --ok: #15803d; --warn: #b45309; --err: #b91c1c; --ink: #0f172a; --muted: #64748b; --line: #e2e8f0; --r: 10px; }
* { box-sizing: border-box; }
.crumbs ol { list-style: none; display: flex; flex-wrap: wrap; gap: 6px; padding: 0; margin: 0 0 14px; font-size: .9rem; }
.crumbs li + li::before { content: "›"; margin-right: 6px; color: var(--muted); }
.crumbs a { color: var(--brand); text-decoration: none; }
.crumbs [aria-current] { color: var(--muted); }
.pages { display: flex; gap: 6px; list-style: none; padding: 0; }
.pages a { display: grid; place-items: center; min-width: 38px; height: 38px; border: 1px solid var(--line); border-radius: 8px; text-decoration: none; color: var(--ink); }
.pages a:hover { border-color: var(--brand); }
.pages a[aria-current="page"] { background: var(--brand); color: #fff; border-color: var(--brand); }
</style>
<nav class="crumbs" aria-label="Breadcrumb"><ol><li><a href="#">Home</a></li><li><a href="#">Students</a></li><li><span aria-current="page">Class 10-A</span></li></ol></nav>
<nav aria-label="Pagination"><ul class="pages"><li><a href="#" aria-label="Previous">‹</a></li><li><a href="#">1</a></li><li><a href="#" aria-current="page">2</a></li><li><a href="#">3</a></li><li><a href="#" aria-label="Next">›</a></li></ul></nav>CSS-only tabs (radio hack) and accordion
<style>
:root { --brand: #264de4; --brand-600: #1e40af; --ok: #15803d; --warn: #b45309; --err: #b91c1c; --ink: #0f172a; --muted: #64748b; --line: #e2e8f0; --r: 10px; }
* { box-sizing: border-box; }
.tabs { display: flex; flex-wrap: wrap; border-bottom: 2px solid var(--line); }
.tabs input { position: absolute; opacity: 0; }
.tabs label { padding: 10px 16px; cursor: pointer; color: var(--muted); font-weight: 600; border-bottom: 3px solid transparent; margin-bottom: -2px; }
.tabs .panel { order: 99; width: 100%; padding: 14px 4px; display: none; }
.tabs input:checked + label { color: var(--brand); border-color: var(--brand); }
.tabs input:checked + label + .panel { display: block; }
.tabs input:focus-visible + label { outline: 3px solid #f59e0b; }
details.acc { border: 1px solid var(--line); border-radius: var(--r); margin: 8px 0; overflow: hidden; }
details.acc summary { padding: 12px 14px; cursor: pointer; font-weight: 600; list-style: none; display: flex; justify-content: space-between; }
details.acc summary::after { content: "+"; transition: transform .2s; }
details.acc[open] summary::after { transform: rotate(45deg); }
details.acc p { margin: 0; padding: 0 14px 12px; color: var(--muted); }
</style>
<div class="tabs">
<input type="radio" name="t" id="t1" checked><label for="t1">Description</label><div class="panel">Cashew fudge made with A-grade kaju and pure ghee.</div>
<input type="radio" name="t" id="t2"><label for="t2">Ingredients</label><div class="panel">Cashew 60%, sugar 38%, ghee 2%, silver vark.</div>
<input type="radio" name="t" id="t3"><label for="t3">Reviews (128)</label><div class="panel">★★★★★ “Best kaju katli in Jaipur.”</div>
</div>
<details class="acc"><summary>Shelf life</summary><p>Up to 15 days at room temperature.</p></details>
<details class="acc"><summary>Delivery areas</summary><p>All of Jaipur within 2 hours.</p></details>The radio trick is perfect for simple product pages. For full ARIA tab semantics (arrow-key navigation, role="tablist") you will write a small script in the JS course – the CSS stays the same.
Modal dialog and toast
<style>
:root { --brand: #264de4; --brand-600: #1e40af; --ok: #15803d; --warn: #b45309; --err: #b91c1c; --ink: #0f172a; --muted: #64748b; --line: #e2e8f0; --r: 10px; }
* { box-sizing: border-box; }
dialog.modal { border: 0; border-radius: 16px; padding: 0; width: min(92vw, 380px); box-shadow: 0 30px 60px rgb(0 0 0 / 30%); }
dialog.modal::backdrop { background: rgb(15 23 42 / 55%); backdrop-filter: blur(2px); }
dialog.modal[open] { animation: pop .2s ease-out; }
@keyframes pop { from { transform: scale(.95); opacity: 0; } }
.modal header { padding: 16px 18px; border-bottom: 1px solid var(--line); font-weight: 700; }
.modal .body { padding: 16px 18px; color: var(--muted); }
.modal footer { padding: 12px 18px; display: flex; justify-content: flex-end; gap: 8px; background: #f8fafc; }
.toast { position: fixed; left: 50%; bottom: 20px; transform: translate(-50%, 150%); background: var(--ink); color: #fff; padding: 12px 18px; border-radius: 12px; transition: transform .3s ease; }
.toast.show { transform: translate(-50%, 0); }
.btn { font: 600 .9rem system-ui; padding: .55em 1em; border-radius: 8px; border: 1px solid var(--line); background: #fff; cursor: pointer; }
.btn.primary { background: var(--err); color: #fff; border-color: var(--err); }
</style>
<button class="btn" onclick="dlg.showModal()">Delete order #1044</button>
<dialog class="modal" id="dlg" aria-labelledby="dh">
<header id="dh">Delete this order?</header>
<div class="body">Order #1044 (₹720) will be removed. This cannot be undone.</div>
<footer><form method="dialog"><button class="btn">Cancel</button> <button class="btn primary" onclick="setTimeout(function(){toast.classList.add('show'); setTimeout(function(){toast.classList.remove('show')}, 2200)}, 50)">Delete</button></form></footer>
</dialog>
<div class="toast" id="toast" role="status">✔ Order #1044 deleted</div>🏅 Job skills unlocked today
Practice tasks
🏋️ Practice – do it yourself
Show solution
Quick quiz
🧠 Quick Quiz – test yourself
Interview corner
💼 Interview Corner
How do you build a reusable component library in plain CSS?
Define design tokens as custom properties, create a base class per component with modifier classes for variants/sizes (BEM), keep selectors flat (single class), document usage, and avoid page-specific overrides inside components.
Native <dialog> vs a div modal?
dialog with showModal() handles the top layer, focus trapping, Esc to close, inert background and ::backdrop natively – fewer bugs and better accessibility than a custom div modal.
Summary
- Components = base class + modifiers, driven by tokens.
- Badges, avatars, alerts, breadcrumbs, pagination are small patterns reused everywhere.
- Tabs via radio + :checked; accordion via details.
- dialog + ::backdrop for modals; transform for toasts.
Day 17: CSS architecture – BEM naming, file structure, reset, DevTools debugging and performance.
