Everything you need to build a top-notch developer website. Reference-ready, template-portable.
<!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="Your site description for SEO"> <meta property="og:title" content="Your Name | Developer"> <meta property="og:image" content="og-image.png"> <link rel="icon" href="favicon.ico"> <link rel="stylesheet" href="styles.css"> <title>Your Name | Developer</title> </head> <body> <!-- content --> <script src="main.js" defer></script> </body> </html>
Use these for structure, SEO, and accessibility:
| Tag | Purpose |
|---|---|
| <header> | Site or section header, nav |
| <nav> | Navigation links |
| <main> | Primary page content (1 per page) |
| <section> | Thematic grouping with heading |
| <article> | Self-contained content (post, card) |
| <aside> | Tangentially related content |
| <footer> | Footer, credits, links |
| <figure> / <figcaption> | Image + caption wrapper |
| <time datetime=""> | Machine-readable dates |
| <address> | Contact info |
| Tag | Meaning |
|---|---|
| <h1>–<h6> | Headings (one h1 per page) |
| <p> | Paragraph |
| <strong> | Strong importance (bold) |
| <em> | Emphasis (italic) |
| <span> | Inline container (styling only) |
| <div> | Block container (layout only) |
| <a href> | Hyperlink |
| <abbr title> | Abbreviation with tooltip |
| <code> / <pre> | Inline / block code |
| <br> / <hr> | Line break / thematic break |
<!-- Link --> <a href="/about" target="_blank" open new tab rel="noopener noreferrer" security aria-label="About me" >About</a> <!-- Image --> <img src="photo.webp" alt="Descriptive alt text" required! width="800" height="600" prevent CLS loading="lazy" decoding="async" >
<form action="" method="post" novalidate> <label for="email">Email</label> <input type="email" id="email" name="email" required autocomplete="email" placeholder="[email protected]" > <button type="submit">Send</button> </form> <!-- Input types: text email password number tel url search date range color file checkbox radio submit reset button -->
<!-- Unordered --> <ul> <li>Item</li> </ul> <!-- Ordered --> <ol start="1" reversed> <li>First</li> </ol> <!-- Description list (great for key/value) --> <dl> <dt>JavaScript</dt> <dd>Language of the web</dd> </dl>
<!-- SEO --> <meta name="description" content="..."> <meta name="robots" content="index, follow"> <link rel="canonical" href="https://yoursite.com/"> <!-- Open Graph (social sharing) --> <meta property="og:title" content="..."> <meta property="og:description" content="..."> <meta property="og:image" content="og.png"> <meta property="og:url" content="..."> <!-- Twitter Card --> <meta name="twitter:card" content="summary_large_image"> <!-- Performance --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preload" as="image" href="hero.webp">
/* styles.css — put variables in :root */ :root { /* Colors */ --color-bg: #0a0a0f; --color-surface: #13131a; --color-text: #e8e8f0; --color-muted: #7070a0; --color-accent: #6ee7f7; --color-border: #2a2a3d; /* Typography */ --font-sans: 'Inter', sans-serif; --font-display: 'Syne', sans-serif; --font-mono: 'JetBrains Mono', monospace; /* Spacing scale */ --sp-xs: 4px; --sp-sm: 8px; --sp-md: 16px; --sp-lg: 24px; --sp-xl: 40px; --sp-2xl: 64px; /* Radii */ --radius-sm: 6px; --radius-md: 12px; --radius-lg: 20px; /* Transitions */ --transition: all 0.2s ease; --transition-slow: all 0.4s ease; } /* Use like: color: var(--color-accent); */ /* Override for dark mode: */ @media (prefers-color-scheme: light) { :root { --color-bg: #ffffff; } }
/* Modern minimal reset */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } html { scroll-behavior: smooth; text-size-adjust: 100%; } body { font-family: var(--font-sans); background: var(--color-bg); color: var(--color-text); line-height: 1.6; -webkit-font-smoothing: antialiased; } img, video { max-width: 100%; display: block; } button { cursor: pointer; border: none; background: none; } a { color: inherit; text-decoration: none; }
| Selector | Targets |
|---|---|
| .class | Class |
| #id | ID (use sparingly) |
| el > child | Direct children |
| el + sibling | Adjacent sibling |
| el ~ siblings | All siblings after |
| [attr="val"] | Attribute value |
| :hover :focus :active | State |
| :nth-child(2n+1) | Formula-based |
| :not(.class) | Negation |
| ::before ::after | Pseudo-elements |
| :is(h1,h2,h3) | Matches any in list |
| :where() | Zero-specificity :is() |
| :has(img) | Parent with child |
h1, h2, h3, h4 { font-family: var(--font-display); line-height: 1.1; letter-spacing: -0.02em; } h1 { font-size: clamp(2rem, 5vw, 4rem); } h2 { font-size: clamp(1.5rem, 3vw, 2.5rem); } p { max-width: 65ch; } /* optimal line length */ /* Fluid font scale */ font-size: clamp(min, preferred, max); font-size: clamp(1rem, 2.5vw, 1.5rem); /* Text utilities */ text-overflow: ellipsis; white-space: nowrap; overflow: hidden; /* truncation trio */ -webkit-line-clamp: 3; /* multi-line clamp */ display: -webkit-box; -webkit-box-orient: vertical;
/* Gradient backgrounds */ background: linear-gradient(135deg, #0a0a0f, #1c1c28); background: radial-gradient(ellipse at 50% 0%, #6ee7f733, transparent 70%); /* Glassmorphism */ background: rgba(255,255,255,0.05); backdrop-filter: blur(16px) saturate(180%); border: 1px solid rgba(255,255,255,0.1); /* Box shadows */ box-shadow: 0 4px 6px rgba(0,0,0,0.1); box-shadow: 0 0 0 2px var(--color-accent); focus ring box-shadow: inset 0 1px 0 rgba(255,255,255,0.1); /* Layered glow */ box-shadow: 0 0 20px rgba(110,231,247,0.3), 0 0 60px rgba(110,231,247,0.1); /* Noise texture overlay */ background-image: url("noise.svg"); /* Clip path shapes */ clip-path: polygon(0 0, 100% 0, 100% 90%, 0 100%);
/* Transition */ transition: transform 0.2s ease, opacity 0.2s ease; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* Keyframe animation */ @keyframes fadeUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .element { animation: fadeUp 0.6s ease forwards; animation-delay: 0.1s; } /* Stagger children */ .item:nth-child(1) { animation-delay: 0.1s; } .item:nth-child(2) { animation-delay: 0.2s; } /* Respect user preferences */ @media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; } }
| Property | Notes |
|---|---|
| aspect-ratio: 16/9 | Maintain ratio |
| object-fit: cover | Image fill |
| overflow: clip | No scrollbar unlike hidden |
| gap: 1rem | Flex/grid spacing |
| place-items: center | grid center shorthand |
| inset: 0 | top/right/bottom/left: 0 |
| pointer-events: none | Click-through |
| user-select: none | Prevent text selection |
| will-change: transform | GPU hint (use sparingly) |
| isolation: isolate | New stacking context |
| contain: layout style | Perf isolation |
| scroll-snap-type: x | Carousel snap |
| accent-color: var(--c) | Style form inputs |
/* Container */ .flex { display: flex; flex-direction: row | column; flex-wrap: wrap; justify-content: flex-start | center | space-between | space-around | space-evenly; align-items: stretch | center | flex-start | flex-end | baseline; align-content: start | center; /* multi-line */ gap: 16px; /* or row-gap / column-gap */ } /* Child */ .item { flex: 1; /* grow, shrink, basis */ flex: 0 0 200px; /* fixed width */ flex-grow: 1; flex-shrink: 0; flex-basis: auto | 50%; align-self: center; order: -1; /* reorder visually */ min-width: 0; /* CRITICAL: prevents overflow */ }
/* Container */ .grid { display: grid; /* Common patterns */ grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); grid-template-columns: 250px 1fr; sidebar + main grid-template-columns: 1fr min(65ch, 100%) 1fr; centered grid-template-rows: auto 1fr auto; header/main/footer gap: 24px 16px; row / column } /* Child placement */ .item { grid-column: 1 / 3; spans 2 cols grid-column: 1 / -1; full width grid-row: 2 / 4; grid-area: header; named areas place-self: center; }
/* Holy Grail layout */ .page { display: grid; grid-template: "header header" auto "nav main " 1fr "footer footer" auto / 200px 1fr; min-height: 100dvh; } /* Sticky footer */ .page { display: flex; flex-direction: column; min-height: 100dvh; } .main { flex: 1; } /* Center anything */ .center { display: grid; place-items: center; } /* Content width with bleed */ .container { width: min(1200px, 100% - 2rem); margin-inline: auto; }
/* Position values */ position: static; /* default, in flow */ position: relative; /* offset from normal */ position: absolute; /* relative to nearest positioned ancestor */ position: fixed; /* relative to viewport */ position: sticky; /* hybrid, needs top/left */ /* Centering with absolute */ .overlay { position: absolute; inset: 0; /* fills parent */ } .centered { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } /* Sticky nav */ .nav { position: sticky; top: 0; z-index: 100; }
/* Mobile-first approach (recommended) */ /* Base = mobile styles */ @media (min-width: 640px) { /* sm */ } @media (min-width: 768px) { /* md */ } @media (min-width: 1024px) { /* lg */ } @media (min-width: 1280px) { /* xl */ } @media (min-width: 1536px) { /* 2xl */ } /* User preferences */ @media (prefers-color-scheme: dark) { } @media (prefers-reduced-motion: reduce) { } @media (hover: none) { } /* touch device */ /* Containers (component-level) */ .card { container-type: inline-size; } @container (min-width: 400px) { .card-inner { display: flex; } }
| Unit | Meaning |
|---|---|
| vw / vh | Viewport width/height |
| dvw / dvh | Dynamic viewport (mobile-safe) |
| svh / lvh | Small / large viewport |
| rem | Root font-size relative |
| em | Parent font-size relative |
| ch | Width of "0" character |
| clamp(a,b,c) | Fluid between min and max |
| min() / max() | Choose smallest/largest |
| % | Relative to parent |
min(100%, 600px) instead of max-width: 600px; width: 100%.// Select elements const el = document.querySelector('.class'); const all = document.querySelectorAll('li'); const id = document.getElementById('main'); // Modify content el.textContent = 'Safe text'; // XSS-safe el.innerHTML = '<b>Bold</b>'; // parsed HTML // Classes el.classList.add('active'); el.classList.remove('active'); el.classList.toggle('active'); el.classList.contains('active'); // → bool // Attributes el.setAttribute('aria-expanded', 'true'); el.getAttribute('href'); el.removeAttribute('hidden'); el.dataset.id; // data-id attribute // Styles el.style.transform = 'translateY(0)'; getComputedStyle(el).getPropertyValue('--var');
// Add / remove listeners el.addEventListener('click', handler); el.removeEventListener('click', handler); // Common events // click dblclick mouseenter mouseleave // keydown keyup keypress // submit input change focus blur // scroll resize load DOMContentLoaded // touchstart touchend // pointerdown pointermove pointerup // Event object function handler(e) { e.preventDefault(); // stop default e.stopPropagation(); // stop bubbling e.target; // element clicked e.currentTarget; // element with listener e.key; // keyboard key e.clientX; e.clientY; // mouse position } // Event delegation (efficient) document.addEventListener('click', (e) => { if (e.target.matches('.btn')) { /* handle */ } });
// Fetch API async function getData(url) { try { const res = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return await res.json(); } catch (err) { console.error(err); } } // Intersection Observer (scroll reveal) const observer = new IntersectionObserver( (entries) => entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('visible'); }), { threshold: 0.1 } ); document.querySelectorAll('.reveal').forEach(el => observer.observe(el)); // ResizeObserver const ro = new ResizeObserver(entries => { /* ... */ }); ro.observe(el);
// Destructuring const { name, age = 0 } = user; const [first, ...rest] = arr; // Spread / Rest const merged = { ...defaults, ...overrides }; const copy = [...arr, newItem]; // Optional chaining user?.address?.city; arr?.[0]?.name; fn?.(); // Nullish coalescing const val = input ?? 'default'; // Template literals const html = `<li class="item">${name}</li>`; // Array methods arr.map(x => x * 2); arr.filter(x => x > 0); arr.reduce((acc, x) => acc + x, 0); arr.find(x => x.id === id); arr.some(x => x.active); arr.every(x => x.valid); arr.flat(Infinity); arr.flatMap(x => [x, x * 2]); // Object methods Object.keys(obj); Object.values(obj); Object.entries(obj); Object.fromEntries(entries);
// localStorage (persists) localStorage.setItem('key', JSON.stringify(data)); const data = JSON.parse(localStorage.getItem('key')); localStorage.removeItem('key'); // sessionStorage (tab-scoped) sessionStorage.setItem('key', value); // URL state (shareable) const params = new URLSearchParams(location.search); params.get('tab'); params.set('tab', 'projects'); history.pushState(null, '', `?${params}`); // Simple state pattern const state = { theme: 'dark', menuOpen: false }; function setState(key, val) { state[key] = val; render(); }
// Clipboard await navigator.clipboard.writeText('copied!'); // Web Animations API el.animate( [{ opacity: 0 }, { opacity: 1 }], { duration: 400, easing: 'ease', fill: 'forwards' } ); // matchMedia const dark = matchMedia('(prefers-color-scheme: dark)').matches; // Scroll el.scrollIntoView({ behavior: 'smooth', block: 'start' }); scrollTo({ top: 0, behavior: 'smooth' }); // Debounce utility function debounce(fn, ms) { let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); }; } const onResize = debounce(() => { /* ... */ }, 200); // Dialog (native modal) dialog.showModal(); dialog.close();
<picture> <source type="image/avif" srcset="img.avif"> <source type="image/webp" srcset="img.webp"> <img src="img.jpg" alt="..." loading="lazy" decoding="async" width="800" height="450"> </picture>
/* Preconnect in <head> */ <link rel="preconnect" href="https://fonts.googleapis.com"> /* CSS font-display */ @font-face { font-family: 'MyFont'; src: url('font.woff2') format('woff2'); font-display: swap; /* swap = text visible immediately */ /* optional = skips if slow */ } /* Self-host for best perf */ /* Use fontsource npm packages */
| Metric | Target |
|---|---|
| LCP (load) | < 2.5s |
| INP (interact) | < 200ms |
| CLS (shift) | < 0.1 |
Prevent CLS: always set width/height on images. Improve LCP: preload hero image. Improve INP: avoid long tasks, use requestIdleCallback.
<!-- defer: executes after HTML parse --> <script src="main.js" defer></script> <!-- async: downloads in parallel, runs ASAP --> <script src="analytics.js" async></script> <!-- module: deferred + ESM --> <script type="module" src="app.js"></script> /* Dynamic import */ const { fn } = await import('./module.js');
/* Use transform/opacity for animation */ /* (composited, no layout/paint) */ transform: translateX(100px); opacity: 0; /* Avoid animating: */ /* width, height, top, left, margin */ /* These trigger layout (expensive) */ /* Contain layout for widgets */ .widget { contain: layout style paint; } /* Critical CSS inline in <head> */ /* Use tools: Critical, PurgeCSS */
<!-- In <head> --> <!-- DNS lookup --> <link rel="dns-prefetch" href="//cdn.example.com"> <!-- Early connection --> <link rel="preconnect" href="https://fonts.gstatic.com"> <!-- Preload critical assets --> <link rel="preload" as="image" href="hero.webp"> <link rel="preload" as="font" href="font.woff2" crossorigin> <!-- Prefetch next pages --> <link rel="prefetch" href="/projects">
<!-- Landmark roles (prefer semantic HTML) --> role="banner" → <header> role="navigation" → <nav> role="main" → <main> role="complementary" → <aside> role="contentinfo" → <footer> <!-- Labels --> aria-label="Close menu" aria-labelledby="heading-id" aria-describedby="desc-id" <!-- States --> aria-expanded="true|false" aria-hidden="true" hide from SR aria-live="polite" dynamic updates aria-current="page" active nav link aria-disabled="true" tabindex="0" make focusable tabindex="-1" focusable by JS only
/* NEVER remove focus outline without replacement */ :focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; border-radius: 4px; } :focus:not(:focus-visible) { outline: none; /* hide for mouse only */ } /* Skip to main content */ .skip-link { position: absolute; top: -40px; left: 0; } .skip-link:focus { top: 0; } /* Trap focus in modals */ // query all focusable, on Tab wrap around const focusable = modal.querySelectorAll( 'button,a,[tabindex]:not([tabindex="-1"])' );
| Level | Normal text | Large text |
|---|---|---|
| AA | 4.5:1 | 3:1 |
| AAA | 7:1 | 4.5:1 |
✅ All images have meaningful alt text (or alt="" for decorative)
✅ Logical heading hierarchy (h1→h2→h3)
✅ All interactive elements keyboard-accessible
✅ Form inputs have associated <label>
✅ Color is not the only way to convey info
✅ Sufficient color contrast (4.5:1 minimum)
✅ Focus indicators visible
✅ No content flashes more than 3x/sec
✅ Page works at 200% zoom
<!DOCTYPE html> <html lang="en"> <head> <!-- [meta, SEO, OG tags — see HTML section] --> <link rel="stylesheet" href="styles.css"> </head> <body> <!-- Skip nav --> <a href="#main" class="skip-link">Skip to main content</a> <header role="banner"> <nav aria-label="Primary navigation"> <a href="/" aria-label="Home">YourName</a> <ul> <li><a href="#about">About</a></li> <li><a href="#projects">Projects</a></li> <li><a href="#contact">Contact</a></li> </ul> <button aria-label="Toggle menu" aria-expanded="false">☰</button> </nav> </header> <main id="main"> <section id="hero" aria-labelledby="hero-heading"> <h1 id="hero-heading">Hi, I'm Your Name</h1> <p>Full-stack developer building [thing]</p> <a href="#projects" class="btn btn-primary">View Work</a> </section> <section id="about" aria-labelledby="about-heading"> <h2 id="about-heading">About</h2> <p>Your bio...</p> <ul aria-label="Skills"> <li>JavaScript</li> </ul> </section> <section id="projects" aria-labelledby="projects-heading"> <h2 id="projects-heading">Projects</h2> <div class="project-grid"> <article class="project-card"> <figure> <img src="project.webp" alt="Project screenshot" loading="lazy"> </figure> <h3>Project Name</h3> <p>Description...</p> <a href="#">View →</a> </article> </div> </section> <section id="contact" aria-labelledby="contact-heading"> <h2 id="contact-heading">Contact</h2> <form method="post" action=""> <!-- inputs --> </form> </section> </main> <footer> <p><small>© 2025 Your Name</small></p> <nav aria-label="Social links"> <a href="https://github.com/you" rel="noopener" target="_blank">GitHub</a> </nav> </footer> <script src="main.js" defer></script> </body> </html>
| Tool | Use |
|---|---|
| Vite | Build tool (fastest) |
| Parcel | Zero-config bundler |
| esbuild | JS bundler/minifier |
| PostCSS | CSS transforms |
| Autoprefixer | Vendor prefixes auto |
| Prettier | Code formatter |
| ESLint | JS linter |
| Stylelint | CSS linter |
| Tool | Style |
|---|---|
| Tailwind CSS | Utility-first |
| Open Props | CSS variables lib |
| PicoCSS | Classless semantic |
| Sass / SCSS | CSS preprocessor |
| CSS Modules | Scoped CSS |
| Stitches | CSS-in-JS |
| Library | Purpose |
|---|---|
| GSAP | Animation (best-in-class) |
| Motion One | Lightweight animation |
| Lenis | Smooth scroll |
| Alpine.js | Lightweight reactivity |
| Zod | Schema validation |
| date-fns | Date manipulation |
| Swiper | Touch sliders |
| Resource | Type |
|---|---|
| Google Fonts | Free webfonts |
| Fontsource | Self-host npm fonts |
| Variable Fonts | fontvariations.com |
| Lucide | Clean icon set |
| Heroicons | Tailwind icons |
| Phosphor | Flexible icon set |
| Tabler Icons | 1800+ free SVGs |
| Platform | Best for |
|---|---|
| Vercel | Static + serverless |
| Netlify | Static + forms |
| Cloudflare Pages | Fastest CDN |
| GitHub Pages | Free static hosting |
_headers file to Netlify/CF for caching + security headers.✅ Validate HTML (validator.w3.org)
✅ Run Lighthouse (Chrome DevTools)
✅ Test keyboard navigation
✅ Test in Firefox + Safari + Chrome
✅ Check mobile (375px, 390px, 414px)
✅ Add 404 page
✅ sitemap.xml + robots.txt
✅ OG image (1200×630px)
✅ favicon.ico + apple-touch-icon
✅ Test with screen reader
index.html / styles.css / main.js at root, with /assets/ for images and fonts. This makes it dead-simple to duplicate for new projects.