A small reusable UI kit built in vanilla JavaScript — Button, Card, Badge, Input Field, Modal, and Toast — each implemented as a self-contained, prop-driven function, mirroring how components work in React (createX(props) → DOM node).
| File | Purpose |
|---|---|
index.html |
Page structure / demo layout |
style.css |
All styling (component styles, dark-mode tokens, catalog page styling) |
script.js |
The 6 reusable component functions + the code that wires up the demo |
Open index.html in a browser — no build step, no dependencies, no server required.
1. Four reusable UI elements as JS functions (plus 2 extra — see below)
createButton(props)—script.jscreateCard(props)—script.jscreateModal(props)—script.jsshowToast(props)—script.js
2. Each accepts parameters instead of being hardcoded
createButton({ text, variant = 'primary', size = 'md', onClick, disabled = false, icon = null, loading = false })
createCard({ title, description = '', tag = '', variant = 'default', footerButtons = [] })
createModal({ title, body = '', actions = null }) // returns { open(), close() }
showToast({ message, type = 'info', duration = 4000 })Every instance on the page is produced by calling these functions with different arguments — there is no duplicated/hand-written markup per variant.
3. Demo page uses each component multiple times with different props/variants
- Buttons: all 4
variants, all 3sizes, adisabledstate, an icon button, and an async loading button. - Cards: 3 cards using the 3
variants, each with a differenttagandfooterButtons. - Modals: 3 separate modal instances (
infoModal,confirmModal,stackedModal) triggered from 2 buttons. - Toasts: 4 buttons for each
type, plus a button that fires 3 toasts in sequence.
4. Modal and Toast are fully functional
- Modal open/close:
.open()/.close()methods; also closes via the × button, a backdrop click, or theEscapekey. - Modal stacking: click "Open info modal" → "Open a stacked modal" — a second dialog layers on top of the first with its own backdrop;
Escapecloses only the topmost modal (tracked via anopenModalsstack array). - Toast auto-dismiss: each toast is removed automatically after its
duration(default 4000ms); a shrinking progress bar visualizes the countdown. - Toast stacking: toasts append to a fixed-position container and stack vertically; click "Fire 3 at once (stack)" to see several independent toasts, each with its own timer, coexist and dismiss separately. Every toast also has a manual × close button.
5. Reflection — how "thinking in components" changed the code
Earlier tasks started from a page and I wrote markup and logic wherever they were needed, so the same button or card ended up re-typed with small inconsistencies each time. Building this kit forced me to start from the element instead: decide what varies (text, variant, size) versus what stays fixed, and write one function that returns a DOM node for any combination of those props. That made the demo page itself much thinner — it's mostly just calls to createButton/createCard/createModal/showToast with different arguments, not hand-written HTML. It also pushed state and behavior (open/close, auto-dismiss timers, stacking order) inside each component's own function instead of scattering them across page-level event listeners, which helped me understand the separation between reusable component logic and the page that uses it — one of the core ideas behind React.
(This is also included directly on the page itself, in the "Build notes" section.)
| Feature | What it does | Where |
|---|---|---|
Badge component (BADGE-600) |
createBadge({ text, tone }) — small status pill in 5 tones |
script.js |
Input Field component (INPUT-500) |
createInput({ label, type, placeholder, value, helper, error, onChange }) — labeled field with helper/error text |
script.js |
| Button loading state | loading prop + setButtonLoading(btn, isLoading) helper — swaps in a spinner and disables the button; demoed on the "Save (async)" button, which simulates a 1.5s request |
script.js |
| Dark / light theme toggle | Button in the header flips data-theme on <html>; every component restyles instantly because colors are driven by shared CSS custom properties, not hardcoded values |
style.css, script.js |
| Copy-usage snippets | Every spec panel has a "Copy usage" button that copies the exact createX({...}) call to the clipboard, with a toast confirmation |
index.html, script.js |
| Prop | Type | Default |
|---|---|---|
text |
string | required |
variant |
primary | secondary | ghost | danger |
primary |
size |
sm | md | lg |
md |
onClick |
function | — |
disabled |
boolean | false |
icon |
string | null |
loading |
boolean | false |
setButtonLoading(btnElement, isLoading, loadingText?) — toggles the spinner/disabled state on a button after it's already been created (for async actions).
| Prop | Type | Default |
|---|---|---|
title |
string | required |
description |
string | "" |
tag |
string | "" |
variant |
default | outlined | elevated |
default |
footerButtons |
array of button props | [] |
| Prop | Type | Default |
|---|---|---|
text |
string | required |
tone |
neutral | info | success | warning | danger |
neutral |
| Prop | Type | Default |
|---|---|---|
label |
string | "" |
type |
string (any HTML input type) | text |
placeholder |
string | "" |
value |
string | "" |
helper |
string | — |
error |
string (overrides helper, adds error styling) |
— |
onChange |
function(value, event) | — |
| Prop | Type | Default |
|---|---|---|
title |
string | required |
body |
string | "" |
actions |
array of button props | [{ text: 'Close', ... }] |
| Prop | Type | Default |
|---|---|---|
message |
string | required |
type |
info | success | warning | danger |
info |
duration |
ms (0 = sticky, no auto-dismiss) |
4000 |