Author: paula

  • CSS Highlights – 2025


    1. Worklet commands


    CSS Worklet commands let CSS run small, performant pieces of JavaScript (called worklets) on a separate thread, enabling advanced visual effects, animations, and layout calculations without blocking the main thread.


    The problem they solve

    Traditionally:

    • JavaScript animations and computations run on the main thread
    • Complex effects can lag or drop frames
    • CSS itself is limited to declarative properties

    CSS worklets bring off-main-thread processing for certain CSS features:

    • Paint worklets → custom painting
    • Layout worklets → custom layout logic
    • Animation worklets → fine-grained animation control

    Types of Worklets

    1️⃣ Paint Worklets

    • Draws custom visuals in CSS
    • Replaces images, gradients, patterns, etc.
    • Runs on a separate thread

    Example:

    // my-paint.js
    registerPaint('stripes', class {
      static get inputProperties() { return ['--stripe-color']; }
      paint(ctx, geom, properties) {
        ctx.fillStyle = properties.get('--stripe-color').toString();
        ctx.fillRect(0, 0, geom.width, geom.height);
        // more painting logic...
      }
    });
    
    .box {
      --stripe-color: red;
      background-image: paint(stripes);
    }
    

    2️⃣ Layout Worklets

    • Create custom layout logic
    • Example: masonry grid, timeline layouts
    • Runs off main thread, so resizing/DOM changes remain smooth

    Example:

    registerLayout('masonry', class {
      layout(children, edges, constraints) {
        // return positions for children
      }
    });
    
    .container {
      display: layout(masonry);
    }
    

    3️⃣ Animation Worklets

    • Control CSS animations in JavaScript but on a compositor thread
    • Enables smooth, complex animations
    • Often combined with typed CSS properties

    Example:

    registerAnimator('spin', class {
      animate(currentTime, effect) {
        effect.localTime = currentTime * 0.001;
      }
    });
    
    .box {
      animation: spin 2s linear infinite;
      animation-timeline: scroll();
    }
    

    Benefits

    • High performance (off main thread)
    • Browser-optimized
    • Enables effects not possible with plain CSS
    • Safe for accessibility (focus / motion preferences respected)

    2. Customizable form elements


    Modern CSS provides new pseudo-classes, pseudo-elements, and properties that let you style native form controls more flexibly and consistently without removing their accessibility or replacing them entirely.


    1️⃣ accent-color

    • Sets the “theme color” for checkboxes, radio buttons, sliders, progress bars
    • Preserves native behavior and accessibility
    • Example:
    input[type="checkbox"] {
      accent-color: seagreen;
    }

    2️⃣ ::marker::placeholder::file-selector-button::part()

    • ::placeholder → style placeholder text
    • ::file-selector-button → style the file input button
    • ::part() → style shadow DOM parts (used in custom elements like <input type="date"> in some frameworks)
    • ::marker → for list bullets (less form, more UI-adjacent)

    Example:

    input::placeholder {
      color: #aaa;
      font-style: italic;
    }
    
    input[type="file"]::file-selector-button {
      background: #0073aa;
      color: white;
      border-radius: 4px;
      padding: 0.25em 0.5em;
    }
    

    3️⃣ :checked:indeterminate:valid:invalid:placeholder-shown:read-only:read-write

    • New or improved pseudo-classes for states
    • Allows styling based on validation or user interaction
    • Example:
    input:invalid {
      border-color: red;
    }
    
    input:valid {
      border-color: green;
    }
    

    4️⃣ ::selection (minor)

    • Lets you style the highlighted text inside inputs
    • Example:
    input::selection {
      background: #0073aa;
      color: white;
    }
    

    5️⃣ appearance: none + native-reset

    • Removes default browser styles to allow full custom styling
    • Often combined with accent-color to maintain native interactions
    • Example:
    select {
      appearance: none;
      padding: 0.5em;
      border-radius: 4px;
      border: 1px solid #ccc;
    }
    

    6️⃣ Typed properties and @property for forms

    • Combine with @property and scroll/animation features to animate form states
    • Example: animate a slider thumb color smoothly

    Why these changes matter

    • Previously, styling checkboxes, radios, sliders, file inputs → required JavaScript hacks or hidden inputs
    • Modern CSS allows accessible, native controls with custom look
    • Works well with dark mode, themes, and responsive design
  • CSS Highlights – 2024


    1. Popover API & Anchor positioning


    The Popover API provides a native, accessible way to show floating UI (menus, tooltips, dialogs) without JavaScript libraries.


    Basic usage

    <button popovertarget="menu">Open menu</button>
    
    <div id="menu" popover>
      Menu content
    </div>

    The browser handles:

    • opening / closing
    • focus management
    • Escape key
    • click-outside dismissal

    Key features

    • No JS required for basic behavior
    • Built-in accessibility
    • Works well with keyboard & screen readers

    Styling a popover

    [popover] {
      padding: 1rem;
      border-radius: 8px;
    }
    

    Popover types

    • popover="auto" (default): dismisses on outside click
    • popover="manual": fully controlled by JS
    • Because a popover has no positioning instructions, the browser uses a safe, default placement: centered in the viewport unless specified otherwise

    Anchor Positioning

    What it is (one sentence)

    Anchor positioning lets you position an element relative to another element in pure CSS, without JavaScript calculations.


    Basic example

    <button id="btn">Info</button>
    
    <div popover anchor="btn">
      Tooltip text
    </div>
    
    [popover] {
      position-anchor: --btn;
      position: absolute;
      inset-area: bottom;
    }
    

    The popover stays attached even if layout changes.


    Why this matters

    Before:

    • JS measured positions
    • window resize listeners
    • fragile calculations

    Now:

    • CSS handles alignment
    • responds to layout automatically
    • less JS, more reliability

    Why they’re often paired

    Popover handles:

    • visibility
    • focus
    • dismissal

    Anchor positioning handles:

    • where it appears
    • how it tracks its anchor

    Together they replace many tooltip / dropdown libraries.


    2. Scroll driven animations


    Scroll-driven animations let CSS animations progress based on scroll position instead of time.


    The problem they solve

    Traditionally:

    • scroll animations → JavaScript scroll listeners
    • poor performance
    • hard to sync with layout

    Scroll-driven animations move this into the browser’s rendering engine.


    Core concepts

    1️⃣ Scroll timelines

    scroll timeline maps scroll progress → animation progress.

    @scroll-timeline scroll-progress {
      source: auto;
    }
    

    (New syntax is evolving)


    2️⃣ animation-timeline

    .box {
      animation: fade-in linear;
      animation-timeline: scroll();
    }
    

    The animation advances as you scroll.


    3️⃣ View timelines (element-based)

    .card {
      animation: slide-in linear;
      animation-timeline: view();
    }
    

    The animation runs as the element enters and leaves the viewport.


    Why this is a big deal

    • compositor-driven (very smooth)
    • browser understands layout + scroll
    • espects prefers-reduced-motion
    • no JS math or listeners

    Typical use cases

    • progress bars
    • parallax effects
    • fade/slide on enter
    • storytelling layouts
    • section reveals

    3. New color functions



    Modern CSS color functions let you define colors in perceptual color spaces, making color adjustments more predictable, accessible, and design-friendly.

    Why new color functions were needed

    Older formats (rgb()hex) are:

    • device-based
    • not perceptually uniform
    • hard to adjust logically (lighten, darken, mix)

    New functions fix this.


    Key modern CSS color functions

    🎨 lab()

    color: lab(60% 40 30);
    
    • Based on CIELAB, a perceptual color space
    • Changes to lightness behave how humans expect
    • Great for accessible color systems

    Mental model:

    “If I increase lightness, it actually looks lighter.”


    lch()

    color: lch(60% 70 40);
    
    • Lightness, Chroma, Hue
    • Easier for designers than LAB
    • Ideal for gradients and themes

    Very popular for design systems


    oklab() & oklch()

    color: oklch(65% 0.15 240);
    
    • Improved perceptual accuracy over LAB/LCH
    • Better hue consistency
    • Increasingly recommended for modern work

    Best choice today for color systems.


    color-mix()

    color: color-mix(in oklch, red 40%, blue);
    
    • Mixes colors in a chosen color space
    • Predictable results
    • Great for hover states and themes

    color()

    color: color(display-p3 1 0.5 0);
    
    • Access to wide-gamut color spaces
    • Supports Display-P3, Rec2020, etc.
    • Enables richer colors on modern displays

    relative-color() (emerging)

    color: relative-color(currentColor lch l +10%);
    
    • Adjusts a color relative to another
    • Ideal for hover / active states
    • Still evolving support

    Why this matters in real projects

    • Better contrast control
    • More consistent themes
    • Easier dark mode
    • Fewer magic numbers

    4. @property (custom properties)


    @property lets you register a CSS custom property with a type, initial value, and inheritance behavior, so the browser can understand and animate it properly.


    The problem it solves

    Normal CSS variables (--x) are:

    • just strings
    • not type-aware
    • not animatable in a meaningful way
    /* This cannot animate smoothly */
    .box {
      --angle: 0deg;
    }
    

    The browser doesn’t know 0deg → 360deg is an angle.


    What @property adds

    When you register a property, you tell the browser:

    • what type it is
    • whether it inherits
    • its initial value
    @property --angle {
      syntax: "<angle>";
      inherits: false;
      initial-value: 0deg;
    }
    

    Now the browser understands --angle as an angle.


    Why this matters (key benefits)

    ✅ Smooth animations

    .box {
      animation: spin 2s linear infinite;
    }
    
    @keyframes spin {
      to {
        --angle: 360deg;
      }
    }
    

    Without @property, this would jump, not animate.


    ✅ Type safety

    syntax: "<number>" | "<length>" | "<color>" | "<angle>";
    

    Invalid values are rejected early.


    ✅ Better performance

    • Browser can animate on the compositor
    • No JS required

    Common use cases

    • animated gradients
    • rotating elements
    • progress indicators
    • scroll-driven animations
    • theme transitions
  • CSS Highlights – 2023


    1. CSS nesting


    CSS nesting lets you write selectors inside other selectors, reducing repetition and making relationships clearer — natively, without preprocessors.


    Basic example

    Before (traditional CSS)

    .card {}
    .card h2 {}
    .card p {}
    .card:hover {}
    

    After (nested CSS)

    .card {
      h2 {}
      p {}
    
      &:hover {}
    }
    

    Same output, cleaner syntax.


    The & 

    & represents the parent selector.

    .button {
      &--primary {}   /* .button--primary */
      &:hover {}      /* .button:hover */
    }
    

    Without &, the selector means a descendant.


    Common nesting patterns

    Descendants (no &)

    .card {
      img {
        border-radius: 8px;
      }
    }
    

    → .card img


    States & modifiers (with &)

    .card {
      &:hover {}
      &.featured {}
    }
    

    Media queries inside components

    .card {
      padding: 1rem;
    
      @media (min-width: 600px) {
        padding: 2rem;
      }
    }
    

    Pseudo-elements

    .link {
      &::after {}
    }
    

    What CSS nesting is NOT

    • ❌ Not Sass-only anymore
    • ❌ Not unlimited deep nesting (keep it shallow)
    • ❌ Not changing specificity rules

    It’s purely syntactic sugar.


    2. Typography


    Around 2023, CSS gained several native typography features that used to require hacks, JavaScript, or extra markup. These give you better control over:

    • text wrapping
    • drop caps and decorative initial letters
    • hyphenation behavior
    • spacing and line control

    🔡 1. text-wrap: balance

    What it does

    Balances text across multiple lines, minimizing rivers or big uneven line breaks — especially useful in headings.

    Example

    h1 {
      text-wrap: balance;
    }
    

    Before:

    Amazing
    New Feature

    After:

    Amazing New
    Feature

    Why it matters
    No JS or manual line breaks, just better line distribution.


    ✨ 2. initial-letter

    What it does

    Creates native “drop caps” or styled initial letters without extra HTML.

    Example

    p::first-letter {
      initial-letter: 3 2;
      font-size: 3rem;
      color: #333;
    }

    Breakdown:

    • 3 → number of lines to span
    • 2 → number of characters affected (optional)

    Result
    Classic magazine-style initials with minimal CSS.


    🧵 3. Better text wrapping & overflow control

    text-wrap variations

    • balance → tries to even out line lengths
    • unbalanced → default behavior

    overflow-wrap

    Already old, but now more consistent:

    p {
      overflow-wrap: break-word;
    }
    

    Ensures long words don’t overflow containers.


    🪶 4. Hyphenation improvements

    CSS

    p {
      hyphens: auto;
    }
    

    What it does

    • Lets the browser insert hyphens when needed
    • Works especially well on narrow columns

    Bonus
    Combine with language:

    <p lang="de">…</p>
    

    Affects how words break correctly in German, French, etc.


    🚀 5. line-break & international text control

    Example

    p {
      line-break: strict;
    }

    Helps with:

    • CJK text (Chinese/Japanese/Korean)
    • Better line break rules per language

    📏 6. More control over spacing

    Features like:

    • text-indent
    • letter-spacing
    • word-spacing
    • line-height

    have become more consistent and well-behaved with modern layout systems (Grid, Flexbox, Container Queries).


    🧠 Why these matter now

    Before:

    • balancing lines → JS or manual <br>
    • drop caps → extra HTML + positioning CSS
    • hyphenation → inconsistent or impossible in some browsers

    Now:

    • purely CSS
    • cleaner HTML
    • responsive-friendly

    3. Trig functions


    CSS now includes a set of trigonometric functions and other math helpers that let you perform math directly in your stylesheets — no JavaScript needed. These are part of the CSS Values and Units Module Level 4 and now have broad support in modern browsers. 


    🔁 Trigonometric functions

    These work like in regular math:

    FunctionWhat it returns
    sin()Sine of an angle (–1 to 1)
    cos()Cosine of an angle (–1 to 1)
    tan()Tangent of an angle
    asin()Inverse sine (number → angle)
    acos()Inverse cosine
    atan()Inverse tangent
    atan2()Angle from two values (y, x)

    You can use angles in units like degrad, and turn

    Example: circle animation (CSS only)

    @keyframes spin {
      to { --angle: 360deg; }
    }
    
    .orbit {
      animation: spin 4s linear infinite;
      left: calc(50px * cos(var(--angle)));
      top: calc(50px * sin(var(--angle)));
    }

    This uses cos() and sin() to position an element around a circle. 


    How this helps real layouts

    You can now:

    • animate orbital or cyclical motion
    • calculate positions with angles
    • create sine-wave motion without JavaScript
    • derive angles using atan2() for UI directions

    These functions are particularly useful in animations, advanced transforms, and visual effects


    Other math functions (context)

    CSS also includes a full suite of math helpers such as:

    • min()max()clamp() – already widely used
    • pow()sqrt()hypot() – exponential math
    • abs() – absolute value
    • round()mod() – rounding and remainder
      These give you even more control over computed values. 

    4. View transitions API (not just CSS!)


    The View Transitions API lets the browser animate visual changes between states or page navigations automatically, without manual animation code.


    The problem it solves

    Traditionally:

    • Page navigation → instant redraw (jarring)
    • State changes → complex JS + CSS animations
    • SPA transitions → lots of bespoke logic

    View Transitions make these native, smooth, and consistent.


    How it works (conceptually)

    1. The browser takes a snapshot of the old view
    2. You update the DOM (navigation or state change)
    3. The browser animates from old → new

    You don’t animate elements directly — the browser does.


    Basic example (same-page state change)

    document.startViewTransition(() => {
      document.body.classList.toggle("dark");
    });

    The browser crossfades the old and new views automatically.


    Element-level transitions

    You can tell the browser which elements correspond between views:

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

    This allows:

    • smooth resizing
    • morphing positions
    • continuity between pages

    Page navigation support

    • Works especially well with SPAs
    • Increasing support for MPAs (same-origin navigation)
    • No JS animation libraries required

    Why it’s powerful

    • Browser-managed animations
    • Highly performant (compositor-level)
    • Consistent motion patterns
    • Respects prefers-reduced-motion
  • CSS Highlights – 2022


    JUMP TO:


    1. Container Queries


    Container queries let a component adapt its styles based on the size of its container, not the viewport.

    They solve the problem media queries never could.


    Why they exist

    Media queries answer:

    “How big is the screen?”

    Container queries answer:

    “How much space does this component have?”

    This is essential for:

    • reusable components
    • cards in grids
    • sidebars vs main content
    • CMS layouts (very relevant to WordPress)

    Minimal example

    .card {
      container-type: inline-size;
    }
    
    @container (min-width: 400px) {
      .card {
        display: flex;
        gap: 1rem;
      }
    }
    

    If .card is wider than 400px, it changes layout — regardless of screen size.


    Two steps you must remember

    1️⃣ Declare a container

    container-type: inline-size;
    

    This opts the element into being a queryable container.


    2️⃣ Query the container

    @container (min-width: 400px) { ... }
    

    Not @media.


    Container types (quick)

    • inline-size → respond to width (most common)
    • size → respond to both width and height

    Naming containers (optional but useful)

    .card {
      container-type: inline-size;
      container-name: card;
    }
    
    @container card (min-width: 400px) {
      ...
    }
    

    Prevents accidental matches.


    What container queries replace

    Before:

    • viewport-based hacks
    • duplicated components
    • JS resize observers

    Now:

    • components are truly portable
    • CSS stays declarative

    2. Color Spaces and Functions


    Why new CSS color spaces were added

    Traditional CSS colors (rgb()hexhsl()) are based on sRGB, which:

    • was designed for old monitors
    • can’t represent many modern display colors
    • interpolates colors poorly (muddy gradients)

    Modern CSS color features solve this by:

    • supporting wider gamuts
    • using perceptually uniform color spaces
    • giving better control and math

    The big new color spaces

    1️⃣ oklch() (most important)

    color: oklch(60% 0.15 240);
    

    What it is

    • A perceptually uniform color space
    • Based on how humans actually see color

    Components

    • L → lightness (0–100%)
    • C → chroma (color intensity)
    • H → hue (angle)

    Why it matters

    • Changing lightness doesn’t shift hue
    • Gradients look natural
    • Ideal for design systems

    👉 This is the future default choice


    2️⃣ oklab()

    color: oklab(0.6 -0.1 -0.05);
    
    • Same family as oklch
    • Cartesian version (less intuitive)
    • Mostly used internally or for math

    Most people prefer oklch().


    3️⃣ lab() / lch()

    color: lch(65% 40 130);
    
    • Older perceptual color spaces
    • Still better than hsl()
    • Slightly less accurate than OKLCH

    Think of these as stepping stones.


    4️⃣ Display color spaces (color())

    color: color(display-p3 1 0 0);
    

    Supported spaces:

    • display-p3
    • rec2020
    • a98-rgb
    • prophoto-rgb

    These unlock more vivid colors on modern screens (Apple devices especially).


    New & updated color functions

    1️⃣ Modern rgb() and hsl() syntax

    color: rgb(255 0 0 / 50%);
    color: hsl(0 100% 50% / 0.5);
    
    • Spaces instead of commas
    • Built-in alpha channel
    • Matches other color functions

    2️⃣ color-mix() (very useful)

    background: color-mix(in oklch, red 70%, white);
    

    Mixes colors correctly, in a chosen color space.

    Better than manual rgba tricks.


    3️⃣ Relative colors (powerful)

    --brand: oklch(60% 0.2 250);
    
    color: oklch(from var(--brand) calc(l + 10%) c h);
    

    This means:

    “Use the same color, but lighter”

    Perfect for:

    • hover states
    • borders
    • themes

    4️⃣ color-contrast() (experimental)

    color: color-contrast(white vs black, navy);
    

    Chooses the best contrast color automatically.

    Accessibility-focused, but still evolving.


    Gamut awareness (important concept)

    color: oklch(70% 0.3 30);
    

    If the color:

    • exists on the display → shown
    • doesn’t exist → browser clips safely

    This avoids broken colors.


    When you should care

    Use new color features if you:

    • build design systems
    • care about accessibility
    • want consistent hover / active states
    • design on modern displays
    • want better gradients

    You don’t need them for:

    • quick one-off sites
    • legacy browser support

    Simple mental model

    Old worldNew world
    hexoklch()
    rgba()color-mix()
    hsl()perceptual lightness
    guessworkmath-friendly colors

    Example: modern button (clean & future-proof)

    :root {
      --brand: oklch(60% 0.18 250);
    }
    
    button {
      background: var(--brand);
    }
    
    button:hover {
      background: color-mix(in oklch, var(--brand) 85%, black);
    }
    

    Predictable. Accessible. Maintainable.



    3. :has() pseudo selector


    :has() lets you style an element based on what it contains or what comes after it — effectively a “parent selector” in CSS.


    Example:

    .card:has(img) {
      border: 2px solid green;
    }
    

    Means:

    “Style .card if it contains an <img>.”


    Common, practical uses

    Style a parent when a child exists

    form:has(input:invalid) {
      border: 2px solid red;
    }
    

    Style based on sibling state

    label:has(+ input:checked) {
      font-weight: bold;
    }
    

    Layout adjustments

    .article:has(.sidebar) {
      grid-template-columns: 3fr 1fr;
    }
    

    Why it’s a big deal

    Before :has():

    • required JavaScript
    • messy extra classes
    • duplicated markup

    Now:

    • pure CSS
    • declarative
    • readable

    Important rules

    • :has() is relational (looks inside or forward)
    • Cannot look up beyond the selected element
    • Can be performance-heavy if overused

    4. New viewport units


    The new viewport units (svhlvhdvh) fix mobile vh bugs by defining viewport sizes that account for dynamic browser UI.

    The problem with old viewport units

    Traditional units:

    • vw → 1% of viewport width
    • vh → 1% of viewport height

    On mobile, vh was unreliable because:

    • browser address bars appear/disappear
    • the “viewport” height changes while scrolling
    • 100vh could be taller than the visible screen

    Result:

    • content cut off
    • unwanted scrolling
    • broken full-height layouts

    The new viewport units

    CSS now defines three viewport concepts:

    Unit typeWhat it represents
    Large (lvhlvw)Maximum possible viewport size
    Small (svhsvw)Minimum possible viewport size
    Dynamic (dvhdvw)Current visible viewport size

    1️⃣ svh — small viewport height

    height: 100svh;
    
    • Uses the smallest viewport
    • Safe from content being hidden
    • Best for layouts that must always fit

    ✅ Good for:

    • full-screen forms
    • login pages
    • modals

    2️⃣ lvh — large viewport height

    height: 100lvh;
    
    • Uses the largest possible viewport
    • Can extend behind browser UI
    • Matches old 100vh behavior

    ⚠️ Rarely what you want now


    3️⃣ dvh — dynamic viewport height (most useful)

    min-height: 100dvh;
    
    • Updates as the UI changes
    • Tracks visible screen size
    • Smoothly adapts on scroll

    ✅ Best for:

    • hero sections
    • full-height layouts
    • modern responsive design

    Safe modern pattern

    .hero {
      min-height: 100svh;
      min-height: 100dvh;
    }
    

    Browsers that support dvh use it.
    Others fall back to svh.


    Width units too

    Same idea applies to width:

    • svw
    • lvw
    • dvw

    Less commonly needed, but useful for:

    • side panels
    • off-canvas menus

    When you should switch

    Replace this:

    height: 100vh;

    With one of:

    • 100dvh → flexible layouts
    • 100svh → strict fit layouts

    5. accent-color


    accent-color lets you set the color of built-in form controls (checkboxes, radio buttons, range sliders, progress bars) without fully restyling them.


    What it affects

    accent-color applies to native UI parts such as:

    • checkboxes
    • radio buttons
    • <input type="range">
    • <progress>

    It does not affect:

    • text inputs
    • buttons
    • selects (beyond limited UA styling)

    Minimal example

    :root {
      accent-color: #0066cc;
    }

    All supported form controls now use that color.


    Per-element control

    input[type="checkbox"] {
      accent-color: green;
    }

    Why it’s useful

    Before accent-color:

    • full custom controls required
    • lots of CSS
    • accessibility pitfalls

    With accent-color:

    • native behavior preserved
    • keyboard & screen-reader support stays intact
    • theming is trivial

    Works well with modern color features

    :root {
      accent-color: oklch(60% 0.18 250);
    }

    Matches your design system colors cleanly.


    Best practices

    • Prefer subtle, accessible colors
    • Ensure sufficient contrast
    • Use it as a theme hint, not full customization

  • CSS Highlights – 2021

    JUMP TO:



    1. aspect-ratio


    aspect-ratio is a modern CSS property that lets you tell the browser “this box should keep this width-to-height proportion” — without hacks, wrappers, or JavaScript.


    What aspect-ratio does

    aspect-ratio: 16 / 9;
    

    This means:

    For every 16 units of width, keep 9 units of height.

    The browser will calculate the missing dimension automatically.


    The simplest example

    <div class="box"></div>
    
    .box {
      width: 300px;
      aspect-ratio: 16 / 9;
      background: lightgray;
    }
    

    Result:

    • width = 300px
    • height = automatically 168.75px

    No padding-bottom tricks needed.


    The key rule

    aspect-ratio only matters when ONE dimension is missing

    If both width and height are set, aspect ratio is ignored.

    /* aspect-ratio is ignored */
    .box {
      width: 300px;
      height: 200px;
      aspect-ratio: 16 / 9;
    }
    

    Common real-world use cases

    1️⃣ Responsive images / videos

    .video {
      aspect-ratio: 16 / 9;
    }
    
    .video iframe {
      width: 100%;
      height: 100%;
    }
    

    Used for:

    • YouTube / Vimeo
    • embedded maps
    • iframes

    2️⃣ Image placeholders (no layout shift)

    img {
      aspect-ratio: 3 / 2;
      width: 100%;
      object-fit: cover;
    }
    

    Prevents content jumping while images load (important for performance).


    3️⃣ Cards with consistent shapes

    .card-image {
      aspect-ratio: 1 / 1;
      background-size: cover;
    }
    

    Perfect for:

    • product grids
    • galleries
    • blog thumbnails

    Aspect ratio with Grid & Flexbox

    Grid example

    .grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 1rem;
    }
    
    .item {
      aspect-ratio: 4 / 3;
      background: #ddd;
    }
    

    Each item:

    • auto-sizes height
    • stays proportional
    • fills the grid cell width

    Aspect ratio on replaced elements (images, video)

    Images already have an intrinsic aspect ratio.

    img {
      width: 100%;
      height: auto;
    }
    

    But CSS aspect-ratio can override or enforce consistency:

    img {
      aspect-ratio: 1 / 1;
      object-fit: cover;
    }
    

    aspect-ratio vs the old padding hack

    Old way (avoid now)

    .box {
      position: relative;
      padding-bottom: 56.25%; /* 16:9 */
    }
    
    .box iframe {
      position: absolute;
      inset: 0;
    }
    

    New way (preferred)

    .box {
      aspect-ratio: 16 / 9;
    }
    

    Cleaner, readable, maintainable.


    Browser support (important part)

    Current support (modern reality)

    • ✅ Chrome
    • ✅ Edge
    • ✅ Firefox
    • ✅ Safari
    • ✅ iOS Safari
    • ✅ Android Chrome

    Support is excellent in all modern browsers.

    Older browsers

    • ❌ Internet Explorer
    • ⚠️ Very old mobile browsers

    If you still support IE, you need the padding hack as a fallback.


    Safe fallback pattern (progressive enhancement)

    .box {
      height: 0;
      padding-bottom: 56.25%;
    }
    
    @supports (aspect-ratio: 1 / 1) {
      .box {
        height: auto;
        padding-bottom: 0;
        aspect-ratio: 16 / 9;
      }
    }
    

    Interaction with widthmax-widthmin-width

    .box {
      width: 100%;
      max-width: 400px;
      aspect-ratio: 1 / 1;
    }
    

    The browser:

    1. Calculates width
    2. Derives height from aspect ratio
    3. Applies constraints

    Mental model (easy to remember)

    • Width known → height is calculated
    • Height known → width is calculated
    • Both known → aspect-ratio ignored

    2. Logical Properties


    CSS logical properties may feel “optional” at first, but once they click, they make a lot of sense—especially for internationalisation and modern layouts.


    The core idea (plain English)

    Physical properties describe screen directions:

    • top / right / bottom / left

    Logical properties describe content flow:

    • start / end
    • block / inline

    So instead of saying:

    “Put space on the top

    you say:

    “Put space at the start of the block direction


    Block vs inline (most important concept)

    In normal English text (default)

    writing-mode: horizontal-tb;
    direction: ltr;
    
    • Block direction → top → bottom
    • Inline direction → left → right

    So:

    LogicalPhysical equivalent
    block-starttop
    block-endbottom
    inline-startleft
    inline-endright

    margin-block-start

    .card {
      margin-block-start: 1rem;
    }
    

    This means:

    “Add margin at the start of the block flow

    In normal English text, this behaves exactly like:

    margin-top: 1rem;
    

    Why not margin-start?

    Because start of what?

    CSS separates two axes:

    • Block axis → paragraphs stack
    • Inline axis → text flows

    So you must specify which axis:

    • margin-block-start
    • margin-inline-start

    There is no plain margin-start.


    Common logical property mappings

    Margins & padding

    PhysicalLogical
    margin-topmargin-block-start
    margin-bottommargin-block-end
    margin-leftmargin-inline-start
    margin-rightmargin-inline-end
    padding-leftpadding-inline-start

    Size & positioning

    PhysicalLogical
    widthinline-size
    heightblock-size
    topinset-block-start
    leftinset-inline-start

    Example:

    .box {
      inline-size: 300px;
      block-size: 200px;
    }
    

    Why logical properties exist (the real reason)

    They exist for internationalisation and writing modes.

    Right-to-left languages (Arabic, Hebrew)

    direction: rtl;
    
    • inline-start becomes right
    • inline-end becomes left

    Your layout automatically flips — no extra CSS.


    Vertical writing systems (Japanese, Chinese)

    writing-mode: vertical-rl;
    

    Now:

    • block direction → right → left
    • inline direction → top → bottom

    Logical properties adapt.
    Physical ones break.


    Example: why logical beats physical

    Bad (physical)

    .card {
      margin-left: 1rem;
    }
    

    RTL users get spacing on the wrong side.


    Good (logical)

    .card {
      margin-inline-start: 1rem;
    }
    

    Correct for:

    • LTR
    • RTL
    • vertical text

    Shorthand logical properties

    margin-block: 1rem 2rem;
    margin-inline: 0.5rem 1rem;
    

    Equivalent to:

    margin-top / bottom
    margin-left / right
    

    …but flow-aware.


    Browser support as of 2026

    • ✅ Chrome
    • ✅ Edge
    • ✅ Firefox
    • ✅ Safari
    • ✅ Mobile browsers

    Support is excellent for margins, padding, sizes, and insets.


    Should you use them everywhere?

    Use logical properties when:

    • building reusable components
    • supporting RTL languages
    • writing design systems
    • working with Grid / Flex layouts

    Physical properties are still fine when:

    • doing one-off visual tweaks
    • working on legacy code
    • intentionally breaking symmetry

    3. Cascade Layers


    Cascade layers are a modern CSS feature that give you explicit control over the cascade itself — not just selectors and specificity.


    What cascade layers are (in one sentence)

    Cascade layers (@layer) let you define ordered “buckets” of CSS so some styles always win over others, regardless of selector specificity.


    Why they exist (the problem they solve)

    Traditionally, CSS priority is decided by:

    1. Origin (browser → user → author)
    2. Importance (!important)
    3. Specificity
    4. Source order

    This breaks down when:

    • mixing framework CSStheme CSS, and custom overrides
    • specificity wars (.foo .bar div span)
    • using !important everywhere

    Layers let you say:

    “These styles are more important than those styles — full stop.”


    The simplest example

    @layer base {
      h1 {
        color: black;
      }
    }
    
    @layer overrides {
      h1 {
        color: blue;
      }
    }
    

    Even though both selectors are identical, overrides wins because it’s defined later in the layer order.


    Declaring layer order (best practice)

    @layer reset, base, components, utilities;
    

    This sets the priority once, at the top of your CSS.

    • reset → lowest priority
    • utilities → highest priority

    Putting CSS into layers

    @layer reset {
      * {
        margin: 0;
      }
    }
    
    @layer base {
      body {
        font-family: system-ui, sans-serif;
      }
    }
    
    @layer components {
      .card {
        padding: 1rem;
      }
    }
    
    @layer utilities {
      .text-center {
        text-align: center;
      }
    }
    

    Key rule to remember (very important)

    Layer order beats specificity.

    This wins:

    @layer utilities {
      h1 {
        color: red;
      }
    }
    

    Over this:

    @layer base {
      h1.special.title {
        color: green;
      }
    }
    

    Even though the second selector is much more specific.


    Where unlayered CSS fits

    Unlayered CSS sits above all layers.

    h1 {
      color: purple;
    }
    

    This will override any layered rule.

    This is intentional and useful for quick overrides.


    Nesting layers

    @layer framework.components {
      .btn {
        padding: 1rem;
      }
    }
    

    Creates:

    • framework
    • framework.components

    Order is still controlled at the top.


    Layers vs !important

    @layer!important
    StructuralEmergency override
    PredictableHard to reason about
    Scales wellCauses conflicts
    EncouragedAvoid when possible

    4. content-visibility


    content-visibility is a performance-focused CSS property that tells the browser whether it should bother rendering an element’s contents right now.

    It’s powerful, but subtle.


    What content-visibility does (plain English)

    content-visibility: auto;
    

    Means:

    “If this element is off-screen, skip layout, paint, and rendering of its contents until it’s needed.”

    This can dramatically improve:

    • initial page load
    • time to first render
    • scrolling performance

    The three values you’ll actually use

    visible (default)

    content-visibility: visible;
    

    Browser renders everything as normal.


    hidden

    content-visibility: hidden;
    
    • Contents are not rendered
    • Element still takes up space
    • Similar to visibility: hidden, but stronger

    auto (the important one)

    .content {
      content-visibility: auto;
    }
    
    • Browser renders content only when near viewport
    • Automatically “turns on” when scrolled into view
    • No JavaScript required

    Simple example

    <section class="article">
      <h2>Article title</h2>
      <p>Lots of text…</p>
    </section>
    
    .article {
      content-visibility: auto;
    }
    

    If the section is far below the fold:

    • browser skips rendering it
    • page loads faster

    Very important companion: contain-intrinsic-size

    When content isn’t rendered yet, the browser needs to know how much space to reserve.

    .article {
      content-visibility: auto;
      contain-intrinsic-size: 1000px;
    }
    

    This:

    • prevents layout jumps
    • gives the browser a placeholder size

    You can also do:

    contain-intrinsic-size: 800px 300px;
    

    (height width)


    Where content-visibility shines

    1️⃣ Long pages

    • blog posts
    • documentation
    • product listings
    • event archives (very relevant to WordPress sites)
    .post {
      content-visibility: auto;
      contain-intrinsic-size: 600px;
    }
    

    2️⃣ Repeated heavy components

    • image galleries
    • maps
    • embeds
    • sliders

    3️⃣ Tab panels / accordions

    .tab-panel {
      content-visibility: hidden;
    }
    

    Then switch to visible when active.


    Where not to use it

    🚫 Above-the-fold content
    🚫 Small components
    🚫 Elements that rely on size immediately (JS measurements)
    🚫 Anything needing immediate accessibility focus


    Interaction with JavaScript (important gotcha)

    If JS queries layout info:

    element.getBoundingClientRect();
    

    And the element is not rendered yet:

    • values may be zero or delayed
    • measurements update only when visible

    So:

    Don’t use content-visibility on elements JS depends on immediately.


    Accessibility notes

    • Screen readers can still access content
    • Focused elements force rendering
    • Safe when used for large, scroll-based content

    Still, test with:

    • keyboard navigation
    • screen readers
  • CSS Highlights – 2020

    In an attempt to keep up with the fast paced changes in CSS here are the most significant updates over the past few years. Starting with 2020.

    JUMP TO:



    1. CSS GRID


    HTML

    <div class="grid">
      <header>Header</header>
      <nav>Nav</nav>
      <main>Main content</main>
      <aside>Sidebar</aside>
      <footer>Footer</footer>
    </div>
    

    CSS

    .grid {
      display: grid;
    
      /* 1️⃣ Define columns and rows */
      grid-template-columns: 200px 1fr 150px;
      grid-template-rows: auto 1fr auto;
    
      /* 2️⃣ Name layout areas */
      grid-template-areas:
        "header header header"
        "nav    main   aside"
        "footer footer footer";
    
      /* 3️⃣ Spacing between items */
      gap: 1rem;
    
      min-height: 100vh;
    }
    
    /* 4️⃣ Assign elements to areas */
    header { grid-area: header; }
    nav    { grid-area: nav; }
    main   { grid-area: main; }
    aside  { grid-area: aside; }
    footer { grid-area: footer; }
    
    /* Just styling so it’s visible */
    .grid > * {
      padding: 1rem;
      background: #eaeaea;
      border: 1px solid #ccc;
    }
    
    /* Responsive Design */
    @media (max-width: 600px) {
      .grid {
        grid-template-areas:
          "header"
          "nav"
          "main"
          "aside"
          "footer";
        grid-template-columns: 1fr;
      }
    }

    What this example shows (important features)

    1️⃣ Grid container

    display: grid;

    Turns the .grid div into a grid layout.


    2️⃣ Columns & rows

    grid-template-columns: 200px 1fr 150px;
    grid-template-rows: auto 1fr auto;
    • px → fixed width
    • fr → “take remaining space”
    • auto → size to content

    3️⃣ Named grid areas (very powerful)

    grid-template-areas:
      "header header header"
      "nav    main   aside"
      "footer footer footer";

    This visually describes the layout in CSS.
    You can rearrange the whole layout just by changing these lines.


    4️⃣ Placing items by name

    header { grid-area: header; }
    

    No row/column numbers needed — much more readable.


    5️⃣ Gaps

    gap: 1rem;
    

    Replaces old hacks like margins for spacing grid items.


    Why Grid is useful (vs Flexbox)

    • Grid → 2-dimensional (rows and columns)
    • Flexbox → 1-dimensional (row or column)

    This example would be awkward in Flexbox but clean in Grid.


    2. prefers-color-scheme


    prefers-color-scheme is a media query that lets your CSS respond to the user’s OS or browser theme preference:

    • light
    • dark

    The browser decides this based on system settings (macOS, Windows, iOS, Android).


    HTML (unchanged for both themes)

    <div class="card">
      <h1>Hello</h1>
      <p>This page adapts to your system theme.</p>
      <button>Click me</button>
    </div>
    

    CSS

    /* 1️⃣ Default (light theme fallback) */
    :root {
      --bg: #ffffff;
      --text: #222222;
      --card: #f4f4f4;
      --accent: #0066cc;
    }
    
    body {
      background: var(--bg);
      color: var(--text);
      font-family: system-ui, sans-serif;
      padding: 2rem;
    }
    
    .card {
      background: var(--card);
      padding: 2rem;
      border-radius: 8px;
      max-width: 400px;
    }
    
    button {
      background: var(--accent);
      color: white;
      border: none;
      padding: 0.6rem 1rem;
      border-radius: 4px;
    }
    
    /* 2️⃣ Dark mode overrides */
    @media (prefers-color-scheme: dark) {
      :root {
        --bg: #0f172a;
        --text: #e5e7eb;
        --card: #1e293b;
        --accent: #60a5fa;
      }
    }
    

    What this example demonstrates

    1️⃣ Media query, not a class

    @media (prefers-color-scheme: dark)
    
    • No JavaScript
    • No .dark class
    • Automatically responds to system setting changes

    2️⃣ CSS variables (best practice)

    :root {
      --bg: #ffffff;
    }
    

    Instead of duplicating all styles, we:

    • define variables once
    • override them in dark mode

    This keeps CSS clean and scalable.


    3️⃣ Light mode as a fallback

    /* default styles first */
    

    If a browser doesn’t support prefers-color-scheme, users still get light mode.


    4️⃣ Instant theme switching

    Try changing:

    • macOS → System Settings → Appearance
    • Windows → Personalization → Colors

    The page updates without reload.


    Common patterns in real projects

    Combine with color-scheme

    html {
      color-scheme: light dark;
    }
    

    This tells the browser to:

    • theme form controls
    • scrollbars
    • built-in UI correctly

    Support only dark mode users

    @media (prefers-color-scheme: dark) {
      body {
        background: black;
      }
    }
    

    Force a theme (overrides system)

    body.force-light {
      --bg: white;
    }
    

    Useful if you later add a user toggle.


    Other user-preference media queries.


    All of these belong to the same family:

    Media queryWhat it respects
    prefers-color-schemeLight / dark theme
    prefers-reduced-motionLess animation
    prefers-contrastMore / less contrast
    prefers-reduced-transparencyLess transparency

    They:

    • read OS / browser accessibility settings
    • require no JavaScript
    • update live when the system setting changes

    3. The popularity of Tailwind


    Tailwind CSS is a utility-first CSS framework.

    Instead of writing custom CSS classes, you compose your UI directly in HTML using small, single-purpose classes.

    <button class="bg-blue-600 text-white px-4 py-2 rounded">
      Save
    </button>
    

    Each class maps to one CSS rule:

    • bg-blue-600 → background color
    • px-4 → horizontal padding
    • rounded → border radius

    You rarely write traditional CSS at all.


    How does this differ from the BEM approach?

    What BEM is:

    BEM (Block-Element-Modifier) is not a framework.
    It’s a naming convention and architecture for writing CSS.

    <button class="button button--primary">
      Save
    </button>
    
    .button {
      padding: 1rem;
      border-radius: 6px;
    }
    
    .button--primary {
      background: blue;
      color: white;
    }
    

    BEM helps you:

    • structure large CSS codebases
    • avoid selector conflicts
    • understand relationships between components

    Core difference

    Tailwind replaces most custom CSS.
    BEM organizes custom CSS.

    They’re solving different layers of the problem.


    Side-by-side comparison

    AspectTailwind CSSBEM
    TypeCSS frameworkNaming methodology
    Where styles liveMostly in HTMLMostly in CSS
    CSS you writeMinimalPrimary
    Naming responsibilityTailwind handles namingYou design names
    Learning curveMedium (many utilities)Low–medium
    File sizeGenerated & purgedGrows with project
    Best forRapid UI developmentLong-term CSS architecture

    Example: same button, different approach

    Tailwind

    <button class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded transition">
      Save
    </button>
    

    No CSS file needed.


    BEM

    <button class="btn btn--primary">
      Save
    </button>
    
    .btn {
      padding: 0.5rem 1rem;
      border-radius: 0.5rem;
      transition: background 200ms;
    }
    
    .btn--primary {
      background: blue;
      color: white;
    }
    
    .btn--primary:hover {
      background: darkblue;
    }
    

    How they scale

    Tailwind scaling model

    • Reuse comes from copying patterns
    • Consistency comes from design tokens
    • Changes are fast and local
    <div class="max-w-md mx-auto p-6 bg-white shadow-lg rounded-lg">
    

    BEM scaling model

    • Reuse comes from shared components
    • Consistency comes from discipline
    • Changes are centralized but slower
    .card {}
    .card__header {}
    .card__body {}
    .card--featured {}
    

    Mental model difference

    Tailwind

    “Describe how it looks, right here, right now.”

    BEM

    “Describe what this thing is, and style it elsewhere.”


    Common criticisms

    Tailwind critics say:

    • HTML becomes cluttered
    • Harder to read at first
    • Requires a build step

    BEM critics say:

    • Boilerplate-heavy
    • Lots of class names
    • CSS files grow large over time

    Both are valid — it depends on context.


    Can they be used together?

    Yes — and often they are.

    Example:

    <button class="btn bg-blue-600 text-white px-4 py-2">
    
    • btn → semantic hook (BEM-ish)
    • Tailwind → actual styling

    This is common in WordPress and React projects.


    When Tailwind is a good fit

    • Component-based apps (React, Vue)
    • Design systems
    • Teams that want speed + consistency
    • You want fewer CSS files

    When BEM is a good fit

    • Traditional multi-page sites
    • CMS-driven sites (WordPress, Drupal)
    • Teams that prefer semantic HTML
    • Long-lived projects with lots of CSS
  • Bouncing Spider

    Bouncing Spider

    How is the spider bouncing around the screen?

    What this simple code does:

    • Creates a little black circle (#spider).
    • Moves it by adding velocity to x and y each frame.
    • Reverses velocity when it hits a wall → bouncing effect.
    • Runs smoothly using requestAnimationFrame.

    1. The browser has a built-in animation loop

    Browsers try to draw the screen about 60 times per second (60 FPS).

    requestAnimationFrame() lets you say:

    “Please run this function right before the browser draws the next frame.”

    So it syncs your code with the screen refresh.


    2. You give requestAnimationFrame() a function

    requestAnimationFrame(animate);
    

    3. The animate() function calls requestAnimationFrame again

    function animate() {
      // move the spider
      // bounce it off walls
      requestAnimationFrame(animate);
    }
    

    This is crucial.

    Every time animate() runs, it asks the browser to run it again on the next frame.

    That creates an endless loop:

    4. Why not use setInterval instead?

    You could animate using:

    setInterval(animate, 16); // ~60fps

    BUT:

    • requestAnimationFrame is smoother,
    • it pauses automatically when the tab is hidden (saves battery),
    • it syncs to the monitor’s refresh rate,
    • it’s the standard for modern animations.

    5. In the code…

    This line:

    requestAnimationFrame(animate);

    means:

    “Run animate() again at the next frame so the spider keeps moving.”

    Without it, the spider would move once and stop.


    HTML

    <div id="spider"></div>

    CSS

    body {
        margin: 0;
        overflow: hidden;
      }
    
      /* tiny spider */
      #spider {
        width: 40px;
        height: 40px;
        background: black;
        border-radius: 50%;
        position: fixed;
      }

    Javascript

    const spider = document.getElementById("spider");
    
      // starting position
      let x = 100;
      let y = 100;
    
      // velocity (px per frame)
      let vx = 6;  
      let vy = 4;
    
      function animate() {
        x += vx;
        y += vy;
    
        // bounce off left/right edges
        if (x <= 0 || x + spider.offsetWidth >= window.innerWidth) {
          vx = -vx;
        }
    
        // bounce off top/bottom edges
        if (y <= 0 || y + spider.offsetHeight >= window.innerHeight) {
          vy = -vy;
        }
    
        // update position
        spider.style.left = x + "px";
        spider.style.top = y + "px";
    
        requestAnimationFrame(animate);
      }
    
      animate();
  • Voice Meter

    Voice Meter

    Click on the blue button above to see the animation!

    How does this work?

    The .meter CSS class :

    • puts the bars next to each other
    • spaces them out
    • gives a little room above the button

    Each bar has the .bar class which :

    • starts small (20px tall)
    • has a cyan colour
    • has a soft rounded edge
    • has an animation named pulse

    BUT — the animation is paused until JavaScript starts it. The CSS stagers the animation so each bar start slightly later than the one before. They ripple instead of all moving together.

    The animation CSS says :

    • Start short → grow taller → shrink again.
    • Repeat forever.
    • This is why the bars look like a moving sound meter.

    HTML

    <div class="meter">
      <div class="bar"></div>
      <div class="bar"></div>
      <div class="bar"></div>
      <div class="bar"></div>
      <div class="bar"></div>
    </div>
    
    <button id="toggle">Start Meter</button>

    CSS

    body {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      color: white;
      font-family: sans-serif;
    }
    
    .meter {
      display: flex;
      gap: 6px;
      margin-bottom: 20px;
      height: 60px;
      align-items: flex-end;
    }
    
    .bar {
      width: 10px;
      height: 20px;
      background: #00ffea;
      border-radius: 3px;
      animation: pulse 0.5s ease-in-out infinite;
      animation-play-state: paused; /* JS will toggle this */
    }
    
    .bar:nth-child(1) { animation-delay: 0s; }
    .bar:nth-child(2) { animation-delay: 0.1s; }
    .bar:nth-child(3) { animation-delay: 0.2s; }
    .bar:nth-child(4) { animation-delay: 0.3s; }
    .bar:nth-child(5) { animation-delay: 0.4s; }
    
    @keyframes pulse {
      0%   { height: 20px; }
      50%  { height: 60px; }
      100% { height: 20px; }
    }
    
    button {
      padding: 10px 20px;
      font-size: 1.1rem;
      border: none;
      background: #00ffea;
      color: #111;
      border-radius: 5px;
      cursor: pointer;
    }

    Javascript

    const bars = document.querySelectorAll('.bar');
    const button = document.getElementById('toggle');
    
    let running = false;
    
    button.addEventListener('click', () => {
      running = !running;
    
      bars.forEach(bar => {
        bar.style.animationPlayState = running ? 'running' : 'paused';
      });
    
      button.textContent = running ? 'Stop Meter' : 'Start Meter';
    });
  • Magic Eight Ball

    Magic Eight Ball

    Click on the small magic eight ball to get your question answered!

    HTML

    <div class="eight-ball">
      <p id="answer">Click me!</p>
    </div>

    CSS

    body {
      color: white;
      text-align: center;
      font-family: sans-serif;
      padding-top: 80px;
    }
    
    .eight-ball {
      width: 200px;
      height: 200px;
      margin: auto;
      border-radius: 50%;
      background: radial-gradient(circle, #777, #000);
      display: flex;
      justify-content: center;
      align-items: center;
      cursor: pointer;
      transition: transform 0.6s ease;
    }
    
    .eight-ball.spin {
      transform: rotate(360deg);
    }
    
    #answer {
      font-size: 20px;
      transition: opacity 0.3s;
    }

    Javascript

    const answers = [
      "Absolutely!",
      "Nope!",
      "Maybe...",
      "Try again.",
      "It is certain.",
      "Unclear 🤔",
      "Ask tomorrow!"
    ];
    
    const ball = document.querySelector('.eight-ball');
    const answerText = document.getElementById('answer');
    
    ball.addEventListener('click', () => {
      // Add spin animation
      ball.classList.add('spin');
      answerText.style.opacity = 0;
    
      setTimeout(() => {
        const random = Math.floor(Math.random() * answers.length);
        answerText.textContent = answers[random];
        answerText.style.opacity = 1;
    
        ball.classList.remove('spin');
      }, 600);
    });
  • Animated hamburger menu

    Animated hamburger menu

    Try clicking on the red hamburger icon above to make the navigation menu appear!

    HTML

    <section>
     <div class="flex-container">
        <div class="burger">
          <span class="bar"></span>
          <span class="bar"></span>
          <span class="bar"></span>
        </div>
     </div>
    
      <h1 class="nav" id="menuText">Hello! The navigation menu is here!  </h1>
    </section>
    

    CSS

    section {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        height: 20rem;
        margin: 0;
      }
    
      /* CONTAINER HAS KNOWN SIZE */
      .burger {
        position: relative;
        width: 60px;
        height: 40px;
        background: none;
        border: none;
        cursor: pointer;
      }
    
      /* ALL LINES ARE ABSOLUTELY POSITIONED */
      .burger span {
        position: absolute;
        left: 0;
        width: 100%;
        height: 6px;
        background: red;
        border-radius: 4px;
        transition: 0.35s ease;
        transform-origin: center;
      }
    
      /* POSITIONS OF LINES IN HAMBURGER STATE */
      .burger span:nth-child(1) {
        top: 0;
      }
    
      .burger span:nth-child(2) {
        top: 17px;         /* center bar */
      }
    
      .burger span:nth-child(3) {
        top: 34px;
      }
    
      /* ACTIVE STATE — ALL LINES MOVE TO MIDDLE FIRST */
      .burger.active span:nth-child(1) {
        top: 17px;         /* move to center */
        transform: rotate(45deg);
      }
    
      .burger.active span:nth-child(2) {
        opacity: 0;        /* hide middle bar */
      }
    
      .burger.active span:nth-child(3) {
        top: 17px;         /* move to center */
        transform: rotate(-45deg);
      }
    
      .nav {
        opacity: 0;
        margin-top: 2rem;
        transition: opacity .4s ease;
        color: gray;
      }
    
      .nav.show {
        opacity: 1;
      }

    Javascript

    const burger = document.querySelector('.burger');
    const nav = document.querySelector('.nav');
    
    burger.addEventListener('click', () => {
        burger.classList.toggle('active');
        nav.classList.toggle('show');
      });