Andrew Flett
Andrew Flett

Modern Web Fundamentals

Invoker Commands

2024–2025
Live Demo

Invoker Command Dialog

Opened declaratively without JavaScript!

Declarative popover content - no JavaScript needed!

Code Example
tsx
<button commandfor="my-dialog" command="show-modal">
  Open Dialog
</button>
<button popovertarget="my-popover">
  Toggle Popover
</button>

<dialog id="my-dialog">
  <p>Opened declaratively!</p>
  <button commandfor="my-dialog" command="close">Close</button>
</dialog>

<div id="my-popover" popover="auto">
  Popover content
</div>

Declarative UI wiring. Buttons can invoke dialogs and popovers without JavaScript event handlers.

View Transitions (CSS)

2024–2025
Live Demo
Code Example
tsx
@view-transition {
  navigation: auto;
}

.item {
  view-transition-name: item;
}

CSS-only view transitions enable smooth animations between DOM states using pure CSS, no JavaScript API required.

Popover API

2024
Live Demo

Native Popover

Built-in focus management, dismissal, and layering!

Code Example
tsx
<button popovertarget="my-popover">
  Toggle
</button>
<div id="my-popover" popover="auto">
  Popover content
</div>

Built-in popover functionality with automatic focus management, dismissal behavior, and proper layering without JavaScript.

CSS Nesting

2024
Live Demo
Hover me!
CSS Nesting without preprocessors
Code Example
tsx
.card {
  padding: 1rem;
  
  & .title {
    font-weight: bold;
    
    &:hover {
      color: blue;
    }
  }
}

Native CSS nesting eliminates the need for preprocessors like Sass, allowing you to nest selectors directly in stylesheets.

CSS Anchor Positioning

2024
Live Demo
Code Example
tsx
.anchor {
  anchor-name: --my-anchor;
}

.tooltip {
  position: absolute;
  position-anchor: --my-anchor;
  bottom: calc(anchor(top) - 0.5rem);
  left: anchor(center);
  transform: translateX(-50%);
}

Position elements relative to other elements using pure CSS. Tooltips, dropdowns, and popovers can be positioned without JavaScript calculations.

Container Queries

2023
Live Demo
Resize me! Size adjusts based on container, not viewport.
Code Example
tsx
.container {
  container-type: inline-size;
}

@container (min-width: 400px) {
  .card { grid-template-columns: 1fr 1fr; }
}

Components respond to their container's size instead of the viewport, enabling truly modular responsive components.

inert Attribute

2023
Live Demo
Code Example
tsx
<div inert>
  <input /> <!-- Cannot be focused -->
  <button>Click</button> <!-- Disabled -->
</div>

Native focus and interaction suppression for modal dialogs and overlays. Disables all user interaction including focus, clicks, and assistive technology.

Viewport Units

2023
Live Demo
100dvh (Dynamic)Adapts to mobile bars
100svh (Small)Bars visible
100lvh (Large)Bars hidden
15dvh height
Code Example
tsx
.hero {
  height: 100dvh; /* Dynamic */
  height: 100svh; /* Small */
  height: 100lvh; /* Large */
}

Dynamic viewport units (dvh, svh, lvh) that account for mobile browser UI, solving the classic mobile viewport height problem.

aspect-ratio

2021
Live Demo
16/9
Code Example
tsx
.video {
  aspect-ratio: 16 / 9;
  width: 100%;
}

Maintain intrinsic aspect ratios without padding hacks. Perfect for responsive images, videos, and containers.

Native Lazy Loading

2019–2020
Live Demo
Lazy loaded

Image loads only when scrolled into view

Code Example
tsx
<img src="image.jpg" loading="lazy" alt="..." />
<iframe src="embed.html" loading="lazy"></iframe>

Images and iframes lazy-load automatically without JavaScript, improving page load performance and reducing bandwidth.

:focus-visible

2022
Live Demo

Focus ring only appears with keyboard navigation

Code Example
tsx
button:focus {
  outline: none;
}

button:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

Keyboard focus styling without ugly mouse outlines. Show focus rings only when users navigate with keyboard, not when clicking with mouse.

:has()

2023
Live Demo
Icon

Parent styling changes based on child presence

Code Example
tsx
/* Style parent when child is checked */
.form:has(input:checked) {
  background: var(--primary-light);
  border-color: var(--primary);
}

/* Style label when input is focused */
.field:has(input:focus) .label {
  color: var(--primary);
}

Parent & relational selectors. CSS can finally react to child state. Select elements based on their descendants or siblings.

Preference Queries

2020–2023
Live Demo
prefers-color-scheme

Responds to system dark/light mode

prefers-reduced-motion

This box pulses unless you prefer reduced motion

prefers-contrast

Gets bolder borders in high contrast mode

Code Example
tsx
@media (prefers-color-scheme: dark) {
  :root { --bg: #1a1a2e; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; }
}

@media (prefers-contrast: more) {
  .card { border: 2px solid currentColor; }
}

Respect user preferences with media queries for color scheme, reduced motion, contrast, and more. Build accessible experiences by default.

Cascade Layers

2022
Live Demo

Component layer overrides base layer

Utility layer wins with !important

Cascade layers give you explicit control over specificity order

Code Example
tsx
@layer reset, base, components, utilities;

@layer base {
  .text { color: gray; }
}

@layer components {
  .text { color: black; } /* Wins! */
}

Explicit control over the cascade with @layer. Define layer order to manage specificity without fighting selector weights.

Web Components

2019

Web Components are a suite of three main web platform APIs that allow you to create custom, reusable, encapsulated HTML elements. The three standards work together to provide a complete solution for building framework-independent components.

Shadow DOM

2019
Live Demo

Decorative component with fully encapsulated styles

Code Example
tsx
const element = document.querySelector('#host');
const shadow = element.attachShadow({ mode: 'open' });

shadow.innerHTML = `
  <style>
    .card {
      background: linear-gradient(135deg, #667eea, #764ba2);
      padding: 1.5rem;
      border-radius: 1rem;
      color: white;
    }
  </style>
  <div class="card">
    <h3>Encapsulated Component</h3>
    <p>Styles are isolated from the rest of the page</p>
  </div>
`;

Shadow DOM provides encapsulation for JavaScript, CSS, and templating. It allows you to attach a hidden, isolated DOM tree to an element, preventing style and script leakage between components.

HTML Templates

2019
Live Demo
Code Example
tsx
<template id="card-template">
  <div class="card">
    <h3><slot name="title">Default Title</slot></h3>
    <p><slot>Default content</slot></p>
  </div>
</template>

<script>
  const template = document.getElementById('card-template');
  const clone = template.content.cloneNode(true);
  document.body.appendChild(clone);
</script>

The <template> element holds HTML that is not rendered immediately but can be cloned and inserted into the document via JavaScript. Templates provide a standard way to declare fragments of markup for later use.

Custom Elements

2019
Live Demo
Code Example
tsx
class MyElement extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <div style="padding: 1rem; background: hsl(var(--muted)); border-radius: 0.5rem;">
        <strong>👋 Custom Element:</strong> ${this.getAttribute('name') || 'World'}
      </div>
    `;
  }
}

customElements.define('my-element', MyElement);

// Usage in HTML
<my-element name="Modern Web"></my-element>

Custom Elements allow you to define your own HTML tags with custom behavior. They have lifecycle callbacks and can be used just like any standard HTML element, providing a way to create reusable components.

Previous
Next