Skip to content

Instantly share code, notes, and snippets.

@peterberkenbosch
Created January 5, 2026 16:57
Show Gist options
  • Select an option

  • Save peterberkenbosch/556521581ef35d92ebc1bc3aa89f9a65 to your computer and use it in GitHub Desktop.

Select an option

Save peterberkenbosch/556521581ef35d92ebc1bc3aa89f9a65 to your computer and use it in GitHub Desktop.
Modern CSS Guide - Comprehensive reference for contemporary CSS features

Modern CSS Guide

This document explains the modern CSS features used in Fizzy's stylesheets. It's written for developers familiar with HTML who want to understand contemporary CSS techniques.


Table of Contents

  1. CSS Layers
  2. CSS Custom Properties (Variables)
  3. CSS Nesting
  4. Modern Selectors
  5. Logical Properties
  6. Modern Units
  7. CSS Functions
  8. Container Queries
  9. Modern Color Formats
  10. CSS Grid
  11. Flexbox and Gap
  12. Aspect Ratio
  13. Media Queries
  14. Feature Queries (@supports)
  15. Animations and Transitions
  16. Scroll Behavior
  17. Modern Pseudo-Classes
  18. Modern Pseudo-Elements
  19. Form Styling
  20. Filters and Blend Modes
  21. CSS Containment
  22. Text Wrapping

CSS Layers

What it does: Organizes CSS rules into named groups that control which styles take priority.

@layer reset, base, components, modules, utilities, native, platform;

Layers listed later win over earlier ones. So platform styles beat utilities, which beat components, and so on.

@layer base {
  body {
    font-family: sans-serif;
  }
}

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

Why use it: Without layers, CSS specificity can be unpredictable. Layers give you explicit control over which styles should override others, regardless of selector complexity or source order.

Files: _global.css, base.css, buttons.css, and most other stylesheets


CSS Custom Properties (Variables)

What it does: Lets you define reusable values that can be referenced throughout your CSS.

:root {
  --inline-space: 1ch;
  --block-space: 1rem;
  --color-link: oklch(57% 0.19 260);
}

.button {
  padding: var(--block-space) var(--inline-space);
  color: var(--color-link);
}

Key concepts:

Syntax Meaning
--name Declares a variable
var(--name) Uses a variable
var(--name, fallback) Uses a variable with a fallback value
:root The <html> element - variables here are global

Math with variables:

--inline-space-half: calc(var(--inline-space) / 2);
--inline-space-double: calc(var(--inline-space) * 2);

Scoped variables: Variables can be redefined in any selector:

:root {
  --text-color: black;
}

.dark-mode {
  --text-color: white;  /* Overrides within .dark-mode */
}

Files: _global.css defines 100+ variables used throughout the project


CSS Nesting

What it does: Lets you write child selectors inside parent selectors, similar to Sass/SCSS but native to CSS.

/* Without nesting */
.card { ... }
.card:hover { ... }
.card .title { ... }
.card.featured { ... }

/* With nesting */
.card {
  ...
  
  &:hover { ... }
  
  .title { ... }
  
  &.featured { ... }
}

The & symbol represents the parent selector:

Nested Selector Compiles To
&:hover .card:hover
&.active .card.active
& .child .card .child
.parent & .parent .card

Real example from buttons.css:

.btn {
  padding: 0.5em 1em;
  
  &[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
  }
  
  &:has(input:checked) {
    background: var(--color-selected);
  }
}

Files: Used extensively in all component stylesheets


Modern Selectors

:is() - Grouping Selector

What it does: Matches any element that matches one of the selectors in its list. Reduces repetition.

/* Without :is() */
.card h1,
.card h2,
.card h3 {
  margin: 0;
}

/* With :is() */
.card :is(h1, h2, h3) {
  margin: 0;
}

Specificity note: Takes the specificity of its most specific argument.

:where() - Zero-Specificity Grouping

What it does: Same as :is() but with zero specificity, making it easy to override.

/* Easy to override later */
:where(#main) {
  padding: 1rem;
}

/* This will win even though #main is an ID */
.custom-main {
  padding: 2rem;
}

:has() - Relational Selector (Parent Selector)

What it does: Selects elements that contain specific children. This is the "parent selector" CSS developers wanted for decades.

/* Select cards that contain an image */
.card:has(img) {
  padding: 0;
}

/* Select inputs that have a checked checkbox */
.btn:has(input:checked) {
  background: blue;
}

/* Select nav when popup is open */
.nav:has(.popup[open]) {
  z-index: 100;
}

:not() - Negation Selector

What it does: Matches elements that don't match the given selector.

/* All paragraphs except those with a class */
p:not([class]) {
  margin-bottom: 1rem;
}

/* Links that aren't buttons */
a:not(.btn) {
  text-decoration: underline;
}

:focus-visible - Keyboard Focus Only

What it does: Matches elements that are focused AND the browser determines focus should be visible (typically keyboard navigation, not mouse clicks).

.btn:focus-visible {
  outline: 2px solid blue;
  outline-offset: 2px;
}

Why use it: Shows focus rings for keyboard users (accessibility) without showing them for mouse users (cleaner UI).

Files: base.css, buttons.css, inputs.css, cards.css


Logical Properties

What it does: Replaces direction-specific properties (left, right, top, bottom) with logical ones that automatically adapt to text direction (LTR or RTL languages).

Mapping Table

Physical Property Logical Property Direction
width inline-size Horizontal in LTR
height block-size Vertical in LTR
margin-left margin-inline-start Start of text
margin-right margin-inline-end End of text
margin-top margin-block-start Start of block
margin-bottom margin-block-end End of block
padding-left/right padding-inline Both horizontal
padding-top/bottom padding-block Both vertical
left inset-inline-start Start position
right inset-inline-end End position
top inset-block-start Block start
bottom inset-block-end Block end
text-align: left text-align: start Start of text
text-align: right text-align: end End of text

Examples

.card {
  inline-size: 100%;           /* width: 100% */
  max-inline-size: 800px;      /* max-width: 800px */
  padding-inline: 1rem;        /* padding-left + padding-right */
  padding-block: 0.5rem;       /* padding-top + padding-bottom */
  margin-inline: auto;         /* margin-left: auto + margin-right: auto */
}

Why use it: Code automatically works for right-to-left languages (Arabic, Hebrew) without any changes.

Files: Used throughout the entire codebase


Modern Units

Relative Units

Unit Meaning Example
rem Relative to root font size (usually 16px) 1rem = 16px
em Relative to current element's font size If element is 20px, 2em = 40px
ch Width of the "0" character Useful for text containers
ex Height of the "x" character Rarely used

Viewport Units

Unit Meaning
vw 1% of viewport width
vh 1% of viewport height
dvw 1% of dynamic viewport width
dvh 1% of dynamic viewport height
svw 1% of small viewport width
svh 1% of small viewport height
lvw 1% of large viewport width
lvh 1% of large viewport height

Dynamic vs Static viewport: On mobile, browser UI (address bar) appears and disappears. dvw/dvh adjust to this, while vw/vh don't.

.hero {
  min-block-size: 100dvh;  /* Full viewport, adjusts for mobile browser UI */
}

Container Query Units

Unit Meaning
cqi 1% of container's inline size
cqb 1% of container's block size
.card-container {
  container-type: inline-size;
}

.card-title {
  font-size: clamp(1rem, 5cqi, 2rem);  /* Scales with container width */
}

Files: _global.css, layout.css, bubble.css, card-columns.css


CSS Functions

calc() - Mathematical Calculations

What it does: Performs math with CSS values, even mixing units.

.sidebar {
  inline-size: calc(100% - 300px);
  padding: calc(var(--space) / 2);
  margin: calc(-1 * var(--bleed));
}

clamp() - Responsive Values

What it does: Creates fluid values with a minimum, preferred, and maximum.

/* clamp(minimum, preferred, maximum) */
.container {
  inline-size: clamp(300px, 80%, 1200px);
}

.title {
  font-size: clamp(1rem, 4vw, 3rem);
}

The value will:

  • Never go below the minimum (300px / 1rem)
  • Never exceed the maximum (1200px / 3rem)
  • Use the preferred value (80% / 4vw) when it's between min and max

min() and max()

.card {
  inline-size: min(100%, 600px);    /* Whichever is smaller */
  font-size: max(16px, 1em);        /* Whichever is larger */
}

Files: _global.css, cards.css, inputs.css, utilities.css


Container Queries

What it does: Lets you style elements based on their container's size, not the viewport.

/* Define a container */
.card-grid {
  container-type: inline-size;
}

/* Query the container */
@container (min-width: 400px) {
  .card {
    display: flex;
    flex-direction: row;
  }
}

@container (max-width: 399px) {
  .card {
    display: block;
  }
}

Container types:

Value Meaning
inline-size Query based on width
size Query based on width AND height
normal No containment (default)

Why use it: Components become truly self-contained. A card can adapt to its container width whether it's in a sidebar, main content, or modal.

Files: card-columns.css, bubble.css, nav.css


Modern Color Formats

OKLCH Color Space

What it does: Defines colors using Lightness, Chroma (saturation), and Hue in a perceptually uniform way.

/* oklch(lightness chroma hue) */
--blue: oklch(57% 0.19 260);
--red: oklch(59% 0.19 38);
Component Range Meaning
Lightness 0% - 100% 0% = black, 100% = white
Chroma 0 - ~0.4 0 = gray, higher = more vivid
Hue 0 - 360 Color wheel angle

With transparency:

background: oklch(var(--lch-black) / 50%);  /* 50% opacity */

Why use it: A 50% lightness blue looks as bright as a 50% lightness red. Colors are perceptually consistent.

color-mix() Function

What it does: Blends two colors together.

/* color-mix(in colorspace, color1 percentage, color2) */
background: color-mix(in srgb, var(--card-color) 10%, white);
border: color-mix(in oklch, currentColor 50%, transparent);

Color spaces: srgb, hsl, oklch, lab, etc.

Files: _global.css, cards.css, rich-text-content.css, golden-effect.css


CSS Grid

What it does: Creates two-dimensional layouts with rows and columns.

Basic Grid

.container {
  display: grid;
  grid-template-columns: 200px 1fr 200px;  /* 3 columns */
  grid-template-rows: auto 1fr auto;        /* 3 rows */
  gap: 1rem;                                /* Space between cells */
}

Grid Fraction Unit (fr)

The fr unit represents a fraction of available space:

grid-template-columns: 1fr 2fr 1fr;  /* Middle column is 2x wider */

Grid Template Areas

Name areas and place items by name:

.layout {
  display: grid;
  grid-template-areas:
    "header header header"
    "nav    main   aside"
    "footer footer footer";
  grid-template-columns: 200px 1fr 200px;
}

.header { grid-area: header; }
.nav    { grid-area: nav; }
.main   { grid-area: main; }
.aside  { grid-area: aside; }
.footer { grid-area: footer; }

Auto-fill and Auto-fit

/* As many 200px columns as will fit */
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));

Files: layout.css, card-columns.css, header.css, nav.css


Flexbox and Gap

Basic Flexbox

.container {
  display: flex;
  flex-direction: row;      /* or column */
  justify-content: center;  /* main axis alignment */
  align-items: center;      /* cross axis alignment */
  flex-wrap: wrap;          /* allow wrapping */
}

The Gap Property

What it does: Adds space between flex/grid children without affecting the outer edges.

.flex-container {
  display: flex;
  gap: 1rem;                    /* Equal horizontal and vertical */
  gap: 1rem 2rem;               /* row-gap column-gap */
  row-gap: 1rem;                /* Vertical only */
  column-gap: 2rem;             /* Horizontal only */
}

Why use it: Replaces margin-based spacing hacks. No need to remove margins from first/last children.

Files: utilities.css, buttons.css, cards.css, nav.css


Aspect Ratio

What it does: Maintains a specific width-to-height ratio.

.avatar {
  aspect-ratio: 1;        /* Square (1:1) */
}

.video {
  aspect-ratio: 16 / 9;   /* Widescreen */
}

.card-image {
  aspect-ratio: 4 / 3;    /* Standard photo */
}

The element will maintain this ratio regardless of its width.

Files: buttons.css, cards.css, bubble.css, nav.css


Media Queries

Viewport-Based

/* Mobile-first approach */
.card { padding: 1rem; }

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

@media (min-width: 960px) {
  .card { padding: 3rem; }
}

Preference-Based

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --background: black;
    --text: white;
  }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

Capability-Based

/* Touch devices */
@media (pointer: coarse) {
  .btn { min-height: 44px; }  /* Larger touch targets */
}

/* Devices with hover capability */
@media (hover: hover) {
  .card:hover { transform: scale(1.02); }
}

/* PWA standalone mode */
@media (display-mode: standalone) {
  .install-prompt { display: none; }
}

/* Print */
@media print {
  .nav, .footer { display: none; }
}

Files: Used throughout; _global.css for theme, reset.css for reduced motion


Feature Queries (@supports)

What it does: Applies styles only if the browser supports a specific CSS feature.

/* Use new feature if supported */
@supports (field-sizing: content) {
  textarea {
    field-sizing: content;  /* Auto-sizing textareas */
  }
}

/* Fallback for browsers without support */
@supports not (gap: 1px) {
  .flex-container > * + * {
    margin-left: 1rem;
  }
}

Files: inputs.css, rich-text-content.css, base.css


Animations and Transitions

Transitions

.btn {
  background: blue;
  transition: background 200ms ease-out;
}

.btn:hover {
  background: darkblue;
}

Transition shorthand: transition: property duration timing-function delay;

Custom Easing Functions

:root {
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out-overshoot: cubic-bezier(0.25, 1.75, 0.5, 1);
}

.modal {
  transition: transform 300ms var(--ease-out-overshoot);
}

Keyframe Animations

@keyframes fade-in {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.card {
  animation: fade-in 300ms ease-out;
}

@starting-style (Entry Animations)

What it does: Defines the starting state for elements that are newly rendered or changed to display: block.

dialog {
  opacity: 1;
  transform: scale(1);
  transition: opacity 200ms, transform 200ms;
  
  @starting-style {
    opacity: 0;
    transform: scale(0.9);
  }
}

allow-discrete Keyword

What it does: Enables transitions on discrete properties like display.

dialog {
  transition: opacity 200ms, display 200ms allow-discrete;
}

Files: animation.css, dialog.css, popup.css, buttons.css


Scroll Behavior

Smooth Scrolling

html {
  scroll-behavior: smooth;
}

Scroll Snap

.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}

.carousel-item {
  scroll-snap-align: start;
}
Property Values
scroll-snap-type x mandatory, y proximity, both mandatory
scroll-snap-align start, center, end

Scrollbar Gutter

.sidebar {
  scrollbar-gutter: stable;  /* Reserve space for scrollbar */
}

Overscroll Behavior

.modal-content {
  overscroll-behavior: contain;  /* Don't scroll parent when hitting edges */
}

Files: utilities.css, search.css, nav.css, card-columns.css


Modern Pseudo-Classes

State-Based

Selector Matches
:hover Mouse over element
:active Being clicked
:focus Has focus
:focus-visible Focus visible (keyboard)
:focus-within Contains focused element
:disabled Disabled form elements
:checked Checked inputs
:placeholder-shown Input showing placeholder
:valid / :invalid Form validation state

Structural

Selector Matches
:first-child First child of parent
:last-child Last child of parent
:nth-child(n) nth child (1-indexed)
:nth-child(odd) 1st, 3rd, 5th...
:nth-child(even) 2nd, 4th, 6th...
:nth-last-child(n) nth from end
:only-child Only child of parent
:empty No children

Advanced nth-child

/* Every 3rd element starting from the 2nd */
:nth-child(3n + 2)

/* First 3 elements */
:nth-child(-n + 3)

/* nth-child with selector filter (new!) */
:nth-child(1 of .comment-by-system)  /* First element matching .comment-by-system */

Files: base.css, cards.css, comments.css


Modern Pseudo-Elements

Selector Creates
::before Content before element
::after Content after element
::backdrop Backdrop behind dialogs/fullscreen
::marker List item markers
::selection Selected text
::placeholder Input placeholder text
::file-selector-button File input button

::backdrop Example

dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity 200ms;
}

dialog[open]::backdrop {
  opacity: 1;
}

Files: dialog.css, popup.css, inputs.css, base.css


Form Styling

accent-color

What it does: Colors native form controls (checkboxes, radio buttons, progress bars).

input[type="checkbox"] {
  accent-color: var(--color-primary);
}

field-sizing

What it does: Makes textareas auto-size to their content.

@supports (field-sizing: content) {
  textarea {
    field-sizing: content;
  }
}

appearance: none

What it does: Removes default browser styling so you can style from scratch.

select {
  appearance: none;
  /* Now you can fully customize it */
}

Files: inputs.css


Filters and Blend Modes

Filter Property

.image {
  filter: brightness(1.1);
  filter: grayscale(100%);
  filter: blur(4px);
  filter: brightness(0.9) contrast(1.1);  /* Multiple */
}
Function Effect
blur(px) Gaussian blur
brightness(n) 0 = black, 1 = normal, 2 = 2x bright
contrast(n) 0 = gray, 1 = normal
grayscale(%) 0% = color, 100% = gray
saturate(n) 0 = gray, 1 = normal, 2 = vivid
hue-rotate(deg) Shift colors on color wheel
drop-shadow() Shadow that follows alpha

Blend Modes

.overlay {
  mix-blend-mode: multiply;     /* Blends with content behind */
  background-blend-mode: overlay; /* Blends background layers */
}

Files: buttons.css, cards.css, golden-effect.css


CSS Containment

What it does: Tells the browser that an element's internals are independent of the rest of the page, enabling performance optimizations.

.card {
  contain: inline-size;  /* Size containment on inline axis */
  contain: layout;       /* Layout containment */
  contain: paint;        /* Paint containment */
  contain: style;        /* Style containment */
  contain: content;      /* layout + paint */
  contain: strict;       /* All containment */
}

container-type: inline-size implicitly adds contain: inline-size.

Files: cards.css, utilities.css


Text Wrapping

text-wrap Property

h1 {
  text-wrap: balance;  /* Even line lengths */
}

p {
  text-wrap: pretty;   /* Avoids orphans/widows */
}

.nowrap {
  text-wrap: nowrap;   /* No wrapping */
}
Value Effect
balance Balances line lengths (good for headlines)
pretty Optimizes for aesthetics, avoiding orphans
nowrap Prevents wrapping
wrap Normal wrapping (default)

Files: cards.css, rich-text-content.css


Summary

This codebase demonstrates modern CSS best practices:

  • Layer-based architecture for predictable cascade
  • Custom properties for maintainable theming
  • Logical properties for internationalization
  • Container queries for truly modular components
  • OKLCH colors for perceptually consistent palettes
  • Fluid typography/spacing with clamp()
  • Modern selectors (:has(), :is(), :where()) for cleaner code
  • Accessibility-first with :focus-visible and reduced motion
  • Progressive enhancement with @supports

The patterns here represent the current state of CSS as of 2024-2025.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment