Web Development Patterns

Working examples I can come back to when I need to see the pieces together.

HTML + CSS Practical

Pattern 01

Semantic Page Structure

A basic page structure using header, navigation, main content, sections, and footer.

View complete example
<body>
  <header>
    <h1>Page Title</h1>
  </header>

  <nav aria-label="Main navigation">
    <a href="/">Home</a>
    <a href="/about">About</a>
  </nav>

  <main>
    <section>
      <h2>Featured Content</h2>
      <p>Page content...</p>
    </section>
  </main>

  <footer>
    <p>Copyright 2026</p>
  </footer>
</body>

Pattern 02

Accessible Data Table

A table with a caption, column headers, and row headers.

View complete example
<table>
  <caption>
    Monthly project hours
  </caption>

  <thead>
    <tr>
      <th scope="col">Project</th>
      <th scope="col">Hours</th>
      <th scope="col">Status</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <th scope="row">Portfolio</th>
      <td>12</td>
      <td>Complete</td>
    </tr>

    <tr>
      <th scope="row">Reference Site</th>
      <td>18</td>
      <td>In Progress</td>
    </tr>
  </tbody>
</table>

Pattern 03

Accessible Contact Form

A simple form with labels, useful input types, and required fields.

View complete example
<form>
  <div>
    <label for="name">
      Name
    </label>

    <input
      type="text"
      id="name"
      name="name"
      autocomplete="name"
      required
    />
  </div>

  <div>
    <label for="email">
      Email
    </label>

    <input
      type="email"
      id="email"
      name="email"
      autocomplete="email"
      required
    />
  </div>

  <div>
    <label for="message">
      Message
    </label>

    <textarea
      id="message"
      name="message"
      rows="5"
    ></textarea>
  </div>

  <button type="submit">
    Send Message
  </button>
</form>

Pattern 04

Responsive Flexbox Navigation

A Flexbox navigation that can wrap when the page gets narrow.

View complete example
.navigation {
  display: flex;
  align-items: center;
  justify-content: space-between;

  gap: 1rem;

  padding: 1rem 2rem;
}

.navigation ul {
  display: flex;
  flex-wrap: wrap;

  gap: 0.75rem;

  margin: 0;
  padding: 0;

  list-style: none;
}

Pattern 05

Responsive CSS Grid

A Grid layout that changes the number of columns based on the available space.

View complete example
.card-grid {
  display: grid;

  grid-template-columns:
    repeat(
      auto-fit,
      minmax(250px, 1fr)
    );

  gap: 1.5rem;
}

Pattern 06

Responsive Reference Card

A reusable card using the same spacing, borders, and colors as this site.

View HTML
<article class="card">
  <h3>CSS Grid</h3>

  <p>
    Learn how grid tracks, gaps,
    and placement work together.
  </p>

  <a href="#">
    View Reference
  </a>
</article>
View CSS
.card {
  padding: 1.5rem;

  background: #ffffff;

  border: 1px solid #b9dce5;
  border-top: 5px solid #3c88a3;
  border-radius: 0.75rem;
}

.card h3 {
  color: #3c88a3;
}

.card a {
  color: #4eabcc;
  font-weight: 600;
}

Pattern 07

Accessible Navigation

A navigation landmark with a label and a current-page link.

View complete example
<nav aria-label="Main navigation">
  <ul>
    <li>
      <a href="/">Home</a>
    </li>
    <li>
      <a href="/reference" aria-current="page">
        Reference
      </a>
    </li>
  </ul>
</nav>

Pattern 09

Visible Keyboard Focus

A clear focus style so keyboard users can see which control is active.

View complete example
:focus-visible {
  outline: 3px solid #276a80;
  outline-offset: 3px;
}

@media (prefers-color-scheme: dark) {
  :focus-visible {
    outline-color: #baf553;
  }
}

Pattern 10

Reduced Motion Preference

A media query that reduces animation when the user has reduced motion turned on.

View complete example
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

Pattern 11

Accessible Modal Dialog

A modal built with the native dialog element and a clear way to close it.

HTML + JavaScript Intermediate

Live result

Save changes?

Your changes are ready to save.

View HTML
<button type="button" id="open-dialog">
  Open dialog
</button>

<dialog id="confirm-dialog" aria-labelledby="dialog-title">
  <h2 id="dialog-title">Save changes?</h2>
  <p>Your changes are ready to save.</p>
  <button type="button" id="close-dialog">Cancel</button>
</dialog>
View JavaScript
const dialog = document.querySelector("#confirm-dialog");

document.querySelector("#open-dialog").addEventListener("click", () => {
  dialog.showModal();
});

document.querySelector("#close-dialog").addEventListener("click", () => {
  dialog.close();
});

Pattern 12

Fetch + API Results

An example of loading, success, empty, and error states for an API request.

JavaScript + API Intermediate

Live result

Choose a state to preview the result.
View JavaScript
let controller;

async function loadProjects() {
  controller?.abort();

  controller =
    new AbortController();

  setStatus("Loading…");

  try {
    const response = await fetch(
      "/api/projects",
      {
        signal: controller.signal
      }
    );

    if (!response.ok) {
      throw new Error(
        `HTTP ${response.status}`
      );
    }

    const projects =
      await response.json();

    if (!projects.length) {
      setStatus(
        "No projects found."
      );
      return;
    }

    renderProjects(projects);
  } catch (error) {
    if (error.name === "AbortError") {
      setStatus(
        "Request cancelled."
      );
      return;
    }

    setStatus(
      "Projects could not be loaded."
    );
  }
}

function cancelRequest() {
  controller?.abort();
}

Pattern 13

Searchable / Filterable List

A small list that filters as the user types and shows how many items match.

JavaScript + DOM Beginner–Intermediate

Live result

5 items

  • HTML semantics
  • CSS Grid
  • Flexbox
  • JavaScript
  • Accessibility
View JavaScript
const input = document.querySelector("#skill-filter");
const items = [...document.querySelectorAll(".skill-list li")];
const status = document.querySelector("#filter-status");

input.addEventListener("input", () => {
  const query = input.value.trim().toLowerCase();
  let visible = 0;

  items.forEach((item) => {
    const matches = item.textContent.toLowerCase().includes(query);
    item.hidden = !matches;
    if (matches) visible += 1;
  });

  status.textContent = `${visible} item${visible === 1 ? "" : "s"}`;
});

Pattern 14

Accordion / FAQ

An accordion using native details and summary elements without extra JavaScript.

HTML Beginner

Live result

What does semantic HTML mean?

It means choosing elements based on the meaning and role of the content.

Do I need JavaScript for this accordion?

No. Native details and summary already provide disclosure behavior.

View HTML
<details>
  <summary>What does semantic HTML mean?</summary>
  <p>
    Choose elements based on the meaning and role
    of the content.
  </p>
</details>

Pattern 15

Accessible Theme Toggle

A button that switches a small example between light and dark themes.

CSS + JavaScript Intermediate

Live result

Theme preview Light

This preview switches independently from the site's current theme.

Reference card Readable text, borders, surfaces, and accents all update together.
View JavaScript
const toggle = document.querySelector("#theme-toggle");
const root = document.documentElement;

toggle.addEventListener("click", () => {
  const nextTheme =
    root.dataset.theme === "dark" ? "light" : "dark";

  root.dataset.theme = nextTheme;
  localStorage.setItem("theme", nextTheme);
});
View CSS
:root {
  --background: #ffffff;
  --text: #1c2529;
}

html[data-theme="dark"] {
  --background: #11191d;
  --text: #f4f8f9;
}

body {
  background: var(--background);
  color: var(--text);
}

Pattern 16

Toast / Status Message

A short status message shown after an action.

JavaScript + Accessibility Intermediate

Live result

View HTML
<button type="button" id="save-button">
  Save changes
</button>

<div id="save-status" role="status" aria-live="polite"></div>
View JavaScript
saveButton.addEventListener("click", async () => {
  await saveChanges();
  saveStatus.textContent = "Changes saved.";
});

Pattern 17

Loading State / Skeleton

A loading placeholder that keeps the same basic space as the finished content.

CSS Intermediate

Live result

View CSS
.skeleton {
  border-radius: 0.35rem;
  background: linear-gradient(
    90deg,
    var(--surface-alt),
    var(--highlight),
    var(--surface-alt)
  );
  background-size: 200% 100%;
  animation: skeleton-shift 1.4s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .skeleton {
    animation: none;
  }
}

Pattern 18

Empty State

A simple empty state that explains why nothing is there and gives the user a next step.

UX + HTML Beginner

Live result

No saved references yet

Save useful entries and they will appear here for quick access.

Browse CSS properties
View HTML
<section class="empty-state" aria-labelledby="empty-title">
  <h2 id="empty-title">No saved references yet</h2>
  <p>
    Save useful entries and they will appear here
    for quick access.
  </p>
  <a href="/references">Browse references</a>
</section>

Pattern 19

Form Success & Error Feedback

Form messages shown close to the field, plus an overall submission message.

Forms + Accessibility Intermediate

Live result

View JavaScript
form.addEventListener("submit", (event) => {
  event.preventDefault();

  if (!email.validity.valid) {
    emailError.textContent = "Enter a valid email address.";
    email.focus();
    return;
  }

  emailError.textContent = "";
  formStatus.textContent = "Form submitted successfully.";
});

Pattern 21

Persistent UI State

A user preference saved in local storage so it can stay available after the page reloads.

JavaScript + Browser API Intermediate

Live result

Compact view: Off

View JavaScript
const savedSetting =
  localStorage.getItem(
    'pattern-compact-view'
  );

if (savedSetting === 'true') {
  setting.classList.add(
    'is-compact'
  );
}

toggle.addEventListener(
  'click',
  () => {
    const compact =
      setting.classList.toggle(
        'is-compact'
      );

    localStorage.setItem(
      'pattern-compact-view',
      String(compact)
    );
  }
);

Pattern 22

Event Delegation

One event listener on a parent element handles actions from its child elements, including items added later.

JavaScript + DOM Intermediate

Live result

  • HTML semantics
  • CSS Grid
  • JavaScript events

3 items

View JavaScript
const list =
  document.querySelector(
    '[data-delegation-list]'
  );

list.addEventListener(
  'click',
  event => {
    const button =
      event.target.closest(
        '[data-remove-item]'
      );

    if (!button) {
      return;
    }

    button.closest('li')?.remove();
  }
);

Pattern 23

Container Query Component

A reusable card that changes its layout based on the space available inside its container.

HTML + CSS Intermediate

Live result

Responsive component

This card changes from a stacked layout to a side-by-side layout when its container becomes wide enough.

View pattern
View HTML
<div class="card-container">
  <article class="responsive-card">
    <div class="card-icon">
      CSS
    </div>

    <div class="card-content">
      <h3>Responsive component</h3>
      <p>
        Component content...
      </p>
    </div>
  </article>
</div>
View CSS
.card-container {
  container-type: inline-size;
}

.responsive-card {
  display: grid;
  gap: 1rem;
}

@container (min-width: 30rem) {
  .responsive-card {
    grid-template-columns:
      auto 1fr;
    align-items: center;
  }
}

Pattern 24

Responsive Image / Media

A responsive image that can provide different sources, sizes, and cropping behavior for different layouts.

HTML + CSS Intermediate

Live result

Jennifer Curtis logo
The image stays inside a responsive frame while preserving a consistent aspect ratio.
View HTML
<picture>
  <source
    media="(min-width: 60rem)"
    srcset="wide-image.webp"
  />

  <img
    src="small-image.webp"
    srcset="
      small-image.webp 480w,
      large-image.webp 1200w
    "
    sizes="
      (min-width: 60rem) 50vw,
      100vw
    "
    alt="Descriptive alternative text"
    loading="lazy"
    width="1200"
    height="675"
  />
</picture>
View CSS
.responsive-media img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

Pattern 25

Accessible Choice Group

A related set of choices grouped with a fieldset and legend so the options have shared context.

HTML + Accessibility Beginner

Live result

Preferred contact method

Choose the method you would prefer me to use first.

View HTML
<fieldset
  aria-describedby="contact-help"
>
  <legend>
    Preferred contact method
  </legend>

  <p id="contact-help">
    Choose one option.
  </p>

  <label>
    <input
      type="radio"
      name="contact-method"
      value="email"
    />
    Email
  </label>

  <label>
    <input
      type="radio"
      name="contact-method"
      value="phone"
    />
    Phone
  </label>
</fieldset>