# Modern CSS

> Writes and reviews modern CSS and HTML with sourced guidance and browser support notes: native dialogs, popovers, select and forms; transitions, anchors, scrolling and view transitions; container and media queries; cascade, typed custom properties and functions; color, typography, gradients, masks and intrinsic layout; responsive units, fluid scales, RTL and mobile browser behavior. Use when building or reviewing a web interface, replacing JavaScript with platform features, adapting a component to its container, resolving responsive design choices or checking CSS against browser targets.

- Skill: `quegenx/modern-css` (Agent Skill, multi-file: 22 files)
- Install (CLI): `npx get-skill add quegenx/modern-css`
- Raw SKILL.md: https://api.skillmd.com/api/skills/quegenx/modern-css/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: Quegenx (https://skillmd.com/u/quegenx)
- Updated: 2026-11-12
- Page: https://skillmd.com/skills/quegenx/modern-css

---


# Modern CSS

HTML and CSS now cover much of the JS work UI libraries did: "popover, anchor, dialog, view transitions, scroll driven animation and custom select" ([nerdy.dev: Headless, boneless, skinless & lifeless UI](https://nerdy.dev/headless-boneless-and-skinless-ui), 2024-10-16, crediting Nils), and models are "barely aware" of it ([nerdy.dev: Why AI Sucks At Front End](https://nerdy.dev/why-ai-sucks-at-front-end), 2026-04-12). This skill holds that CSS. Every fact comes from Adam Argyle's blog [nerdy.dev](https://nerdy.dev), tagged with the post and its date, except [FONTS-AND-TEXT-DETAILS.md](FONTS-AND-TEXT-DETAILS.md) and the few sections tagged with another source (`[Craft]`, `[Emil Kowalski, ...]`, `[Jakub Krehel, ...]`, `[corner-smoothing]`, `[css-modern-features]`, `[responsive-craft]`, `[flornkm, ...]`, `[Libraries.dev]`), [STICKY.md](STICKY.md), and the owner's responsive policy and source comparisons in [RESPONSIVE.md](RESPONSIVE.md) and [RESPONSIVE-FORKS.md](RESPONSIVE-FORKS.md), and every feature carries its current status from MDN (Baseline widely, Baseline newly with its date, or Limited availability with the engines), checked October 2026, stated once in the file that introduces it. Where a post's code or support claim has gone stale, the files give the current form and say what the post said; experimental features stay in, marked. Durations, curves, springs (including `linear()` springs) and staggers for interface motion are in [ui-animation SKILL.md](https://github.com/Quegenx/agent-skills/blob/main/skills/web-ui/ui-animation/SKILL.md); SVG markup, filters and SMIL are in [svg SKILL.md](https://github.com/Quegenx/agent-skills/blob/main/skills/web-ui/svg/SKILL.md); the owner's signed-in app builds its controls, menus and dialogs on headless primitives in [app-ui SKILL.md](https://github.com/Quegenx/agent-skills/blob/main/skills/web-ui/app-ui/SKILL.md), and where that differs from the native route here, the topic file says so; keeping an interface from looking template-built is [anti-slop-ui SKILL.md](https://github.com/Quegenx/agent-skills/blob/main/skills/anti-ai-slop/anti-slop-ui/SKILL.md).

Apply only the routes and checks for the selected job. Read a linked skill's named file from its installed folder when available, or from the linked source. Report an unavailable required reference instead of inventing it. [skill-authoring, DISTRIBUTION.md#composing-skills]

| The job | Read |
|---|---|
| Modal `<dialog>` and shown `[popover]` in the top layer and their support, invoker commands (`commandfor`, `command`), `closedby` light dismiss and its fallback limitation, a component that puts itself in the top layer, `popover="hint"` with `interestfor`, `interactivity: inert`, `CloseWatcher`, transition starter kits for dialogs and popovers, dialog patterns | [POPOVER-AND-DIALOG.md](POPOVER-AND-DIALOG.md) |
| Enter and exit with `@starting-style`, `transition-behavior: allow-discrete`, `display` and `overlay`, why top-layer elements are hard to animate, `[hidden]` exits for any element, removing a node after `getAnimations()` finish, opting in to motion, `<details>` with `::details-content`, the exclusive accordion, `interpolate-size` and `calc-size()`, `sibling-index()` staggers | [TRANSITIONS-AND-STARTING-STYLE.md](TRANSITIONS-AND-STARTING-STYLE.md) |
| `anchor-name`, `position-anchor`, `anchor()`, `anchor-size()`, `position-area`, `position-try-fallbacks`, `position-try-order` and why fallbacks fail in small spaces, anchor-interpolated morphing (AIM), carousel buttons that move inside or outside, other uses | [ANCHOR-POSITIONING.md](ANCHOR-POSITIONING.md) |
| Customizable `<select>` (`appearance: base-select`, `::picker(select)`, `<selectedcontent>`, `:open`, `::checkmark`, `::picker-icon`) and its transitions, the "nice select" build part by part, `field-sizing`, `:user-valid` reveals, form state and values into CSS, range progress fills, radio and checkbox groups, `accent-color`, the on-screen keyboard (`inputmode`, `autocapitalize`, `autocorrect`, `enterkeyhint`) | [SELECT-AND-FORMS.md](SELECT-AND-FORMS.md) |
| Scroller structure and essentials (overscroll, `scroll-axis-lock`), snapping, pull to refresh, `scrollend` and snap events, `scroll-state()` queries, CSS carousels, `touch-action` axes | [SCROLL.md](SCROLL.md) |
| Scroll-driven animation and named timelines, edge-aware scroll fades, scroll-triggered animation, range and shorthand qualifications | [SCROLL-ANIMATIONS.md](SCROLL-ANIMATIONS.md) |
| Sticky that does nothing (`align-self: start`, overflow and `overflow: clip`, insets), offsets shared by several sticky bars, anchor links under a sticky header (`scroll-padding`, `scroll-margin`), detecting the stuck state with an `IntersectionObserver` sentinel, a table with a sticky header row and first column | [STICKY.md](STICKY.md) |
| Scrollbar color and width, the `::-webkit-scrollbar` fallback, hover and focus hints | [SCROLLBARS.md](SCROLLBARS.md) |
| `startViewTransition()`, cross-document `@view-transition`, names, classes and types, text replace transitions, composer and other morphs, 3D perspective, the swoop, freezing a transition to inspect it, where view transitions fall short | [VIEW-TRANSITIONS.md](VIEW-TRANSITIONS.md) |
| Units, fluid scales, container-query migration, right-to-left layout, touch and mobile browser chrome, with the owner's policy and source differences | [RESPONSIVE.md](RESPONSIVE.md) |
| Choose responsive alternatives for sidebars, tables, dashboards, heroes, sticky bars, navigation, forms and grids, with behavior specs and audit gates outside the owner's apps | [RESPONSIVE-FORKS.md](RESPONSIVE-FORKS.md) |
| What a component can know, container size queries (the `container` shorthand, `calc()` and `ch` in the condition), container queries in Tailwind v4 (and the v3 plugin's different syntax), name-only container queries, style queries, media query range syntax, `@custom-media` and feature flags, reduced motion, transparency and data, color scheme and contrast, `forced-colors`, hover and pointer, orientation, color gamut, `scripting: none`, `display-mode` | [QUERIES-AND-PREFERENCES.md](QUERIES-AND-PREFERENCES.md) |
| `@layer` (named, nested, anonymous, later overrides), `@scope` with donut scope and proximity, nesting and BEM, `:has()` and other selectors, focus by demotion, which custom names need dashes and the optional emoji convention, specificity, a study list, the headless, boneless, skinless and lifeless vocabulary | [CASCADE-AND-SCOPE.md](CASCADE-AND-SCOPE.md) |
| The ten kinds of custom properties, typed `@property` and `CSS.registerProperty()`, rotating border beams, the custom property bomb, Open Props and Open Props UI tokens, Tailwind v4 `@theme inline`, live props from JS | [CUSTOM-PROPERTIES.md](CUSTOM-PROPERTIES.md) |
| Mixins and `@apply`, `@function` and `if()`, typed `attr()`, `sibling-index()` and `sibling-count()`, trig and physics, stepped values, functions still coming | [CSS-FUNCTIONS.md](CSS-FUNCTIONS.md) |
| LCH and OKLCH against HSL, OKLCH palettes from one hue, `color-mix()` and its space, relative color, `alpha()`, `contrast-color()`, `light-dark()` and `color-scheme`, page and component light and dark, system and accent colors, P3 with an sRGB fallback, APCA and L* contrast, forced colors | [COLOR.md](COLOR.md) |
| `calc(infinity)`, implementation-dependent numeric ceilings, responsive radius signs and percentage axes, NaN equality boundaries, boolean recipes and persistent state | [CSS-INFINITY.md](CSS-INFINITY.md) |
| `lh` and `rlh`, drop caps, `cap` and the root font units, `text-wrap: balance` and its limits, `text-box` trim, the `GRAD` axis for dark mode and contrast, `text-grow` (prototype), `text-emphasis-style` | [TYPOGRAPHY-AND-UNITS.md](TYPOGRAPHY-AND-UNITS.md) |
| Not from nerdy.dev: typography review (scale, measure, tracking, source-qualified audit checks), properties over raw `font-variation-settings` and `font-feature-settings`, synthesis longhands, axes and OpenType features, optical sizes, small caps and `font-variant-position`, tabular against proportional figures, font smoothing, `hanging-punctuation`, underlines, `::selection`, `::target-text`, highlights and `user-select`, the caret, the iOS input zoom, decorative text, fallback-font metrics (`font-size-adjust`, `@font-face size-adjust`), `hyphenate-limit-chars`, `::marker` and CSS counters, each with its support | [FONTS-AND-TEXT-DETAILS.md](FONTS-AND-TEXT-DETAILS.md) |
| Stops, hints and hard lines, hue interpolation routes, conic charts and spinners, radial and repeating recipes, mesh and noise backgrounds, animated gradient text, borders, `border-image` scrims, shine borders and shadows, `shape()`, `corner-shape` and `superellipse()` and clipping, nested radii with `overflow-clip-margin`, `paint-order` on HTML text, shake, glitch and motion blur | [GRADIENTS-AND-EFFECTS.md](GRADIENTS-AND-EFFECTS.md) |
| Three unintuitive fixes (`min-width: 0`, `flex-shrink: 0`, `auto-fill` columns), grid and subgrid slots, cards aligned with subgrid, stacking in one cell, bento grids, a circle layout with trig and `sibling-index()`, layering without `position: absolute`, balanced wrapping, centering, aspect ratios that follow orientation, logical against physical properties | [LAYOUT.md](LAYOUT.md) |

## The rules

1. **Put floating layers in the top layer with modal `<dialog>` and shown `[popover]`.** "knowing your way around a `<dialog>` and a `[popover]` are table stakes. Otherwise, everyone else will be on top of you, and your wack `z-index` attempts will be defeated with a puny value of `1`"; they come "with no JavaScript to download, and accessibility built in. **Use em**" ([nerdy.dev: 6 CSS Snippets Every Front-End Developer Should Know In 2025](https://nerdy.dev/6-css-snippets-every-front-end-developer-should-know-in-2025), 2025-01-19). app-ui instead builds overlays on headless primitives: "Keep every overlay mounted and pass `open`" ([app-ui OVERLAYS.md](https://github.com/Quegenx/agent-skills/blob/main/skills/web-ui/app-ui/OVERLAYS.md)). See the [modal/modeless distinction](POPOVER-AND-DIALOG.md#use-them-and-the-top-layer).
2. **Interruptible transitions over keyframe animations** ([nerdy.dev: Using @starting-style and transition-behavior for enter and exit stage effects](https://nerdy.dev/using-starting-style-and-transition-behavior-for-enter-and-exit-stage-effects), 2024-05-20). Enter from `@starting-style`, leave with `display: none` delayed by `transition-behavior: allow-discrete` ("you have to use `display: none` to kick something off stage"), "You may also need to transition `overlay` depending on what you're after" for a dialog, and let exits run "slightly faster than entry animations" (the post: .5s in, .4s out). `linear()` springs run long, which makes them "a great fit for transitions and potentially troublesome as keyframes" (2025 snippets post; the springs are in [ui-animation TIMING.md](https://github.com/Quegenx/agent-skills/blob/main/skills/web-ui/ui-animation/TIMING.md#springs)). The post eases entries in and exits out; [ui-animation TIMING.md](https://github.com/Quegenx/agent-skills/blob/main/skills/web-ui/ui-animation/TIMING.md) eases arrivals out and exits in. See [TRANSITIONS-AND-STARTING-STYLE.md](TRANSITIONS-AND-STARTING-STYLE.md) for which engines run the exit.
3. **Enhance, never depend.** The site "tries to only use JavaScript to enhance the experience, not relying on it for the baseline behavior" ([nerdy.dev: New Year, New Site](https://nerdy.dev/new-year-new-site), 2023-01-01). Adopt a newer feature with a cascade fallback that older browsers skip ("If it knows, it knows") or behind `@supports` (2025 snippets post), and put motion inside `@media (prefers-reduced-motion: no-preference)` ([nerdy.dev: Anchor Interpolated Morph (AIM)](https://nerdy.dev/anchor-interpolated-morphing), 2026-01-23). [ui-animation SKILL.md](https://github.com/Quegenx/agent-skills/blob/main/skills/web-ui/ui-animation/SKILL.md) asks for a deliberate reduced version of each animation rather than none.
4. **Put the essentials on every scroller:** "use the `overflow` shorthand and set both axes to prevent surprises; `overscroll-behavior` should always match the scrolling axis; padding and `scroll-padding` should always match; smooth `scroll-behavior` if the use[r] is ok with motion" ([nerdy.dev: Nintendo Switch Homescreen recreated with CSS and a li'l bit of JS](https://nerdy.dev/nintendo-switch-homescreen-css-recreation), 2025-06-13). See [SCROLL.md](SCROLL.md).
5. **Manipulate color in LCH-type spaces.** HSL 50% lightness is not 50% as people see it, "and it will totally bite you when you go to measure color contrast"; "LCH type color spaces should be your colorspace for manipulation" ([nerdy.dev: LCH Luminance vs HSL Lightness](https://nerdy.dev/lch-luminance-vs-hsl-lightness), 2022-12-02). [ui-animation PATTERNS.md](https://github.com/Quegenx/agent-skills/blob/main/skills/web-ui/ui-animation/PATTERNS.md) derives a chart palette in HSL and sets this post against it. See [COLOR.md](COLOR.md).
6. **Know the three strategies for light and dark sections and components** ([nerdy.dev: Page and Component Adaptive Light/Dark](https://nerdy.dev/page-and-component-light-dark-strategies), 2024-08-21): `color-scheme` plus `light-dark()`, "only for color values" but "the HTML is clean and tidy"; `@scope`, whose "primary use case" is "scoping light and dark contexts" (crediting Miriam Suzanne); and, "if you want to adapt more than just color", style queries on custom properties holding the `color-scheme` value, with "each new context" wrapped in a `.dark` or `.light` element. The post filed the last two under Future. app-ui and landing-page-ui switch the whole page with a class (landing-page-ui also sets `data-theme`) set by a head script instead ([app-ui CHECKS.md](https://github.com/Quegenx/agent-skills/blob/main/skills/web-ui/app-ui/CHECKS.md), [landing-page-ui SYSTEM.md](https://github.com/Quegenx/agent-skills/blob/main/skills/web-ui/landing-page-ui/SYSTEM.md)). See [COLOR.md](COLOR.md).
7. **Type the custom properties a system depends on.** `@property` "formalizes CSS system interfaces", "protects CSS systems", "enables new animation powers" and "can perform better when using `inherits: false`" (2025 snippets post). For invalid-value behavior, use the [official correction](CUSTOM-PROPERTIES.md#typed-properties-with-property); the post and old rule incorrectly said the last valid declaration survives.
8. **Dash your own names.** "I think it's a good idea to `--` all your custom names, just to make it really clear they aren't internal CSS keywords" ([nerdy.dev: An Emoji Naming Convention](https://nerdy.dev/css-emoji-convention), 2025-02-20). Both forms are valid where a feature accepts an undashed name, and the owner's app names its containers without dashes (`@container main` in [../app-ui/reference/styles/layout/shell/page-layout.css](https://github.com/Quegenx/agent-skills/blob/main/skills/web-ui/app-ui/reference/styles/layout/shell/page-layout.css)). See [CASCADE-AND-SCOPE.md](CASCADE-AND-SCOPE.md#emoji-in-custom-names).
9. **Give forced colors an affordance:** where shadows and colors separate UI parts, add a transparent 1px border so `forced-colors` mode still draws an edge ([nerdy.dev: New Year, New Site](https://nerdy.dev/new-year-new-site), 2023-01-01); "Transparent borders are your friend!" (Switch homescreen post, 2025-06-13). See [QUERIES-AND-PREFERENCES.md](QUERIES-AND-PREFERENCES.md).
10. **Enhance a `<select>` only for fine pointers:** the custom picker applies under `@media (hover)` and `(pointer: fine)`, and "I keep mobile with the native select" ([nerdy.dev: Nice Select](https://nerdy.dev/nice-select), 2026-02-03). See [SELECT-AND-FORMS.md](SELECT-AND-FORMS.md).
11. **Know the yearly picks.** For 2025: page transitions with `@view-transition { navigation: auto; }` ("the easiest snippet to add to your site, with no downsides"), transitions for `<dialog>`, `[popover]` and `<details>`, animated light and dark gradient text, typed custom properties and `linear()` springs (2025 snippets post). For 2026: `@container scroll-state()`, `text-box` trim, `sibling-index()` staggers and typed `attr()` ([nerdy.dev: 4 CSS Features Every Front-End Developer Should Know In 2026](https://nerdy.dev/4-css-features-every-front-end-developer-should-know-in-2026), 2026-01-07). Several of them still run in one or two engines (cross-document view transitions, `interpolate-size`, `overlay`, scroll-state queries, typed `attr()`), so check each in its topic file before relying on it.
12. **When a model writes the front end, ask for the environment and know its weak spots.** For HTML and CSS "there is no pinning the browser type, browser window size, browser version, the users input type (keyboard, mouse, touch, voice), their user preferences"; the model "doesn't control these, so it ignores them until you make them relevant". "I prompt LLMs with logical properties" ([nerdy.dev: Prompt In Logical Properties](https://nerdy.dev/prompt-in-logical-properties), 2026-01-27); even so, "when you ask for it, or provide documentation that spells it out, it's not guaranteed to work" (Why AI Sucks At Front End post). The post's weak spots: scroll-driven animations and micro-interactions ("it will invent a CSS syntax"), layout and spacing math, combined states, accessibility ("It throws `aria-hidden="true"` at a wall"), performance ("the heaviest, jankiest solution unless you explicitly ask"), and tests (many, not good) (Why AI Sucks At Front End post, 2026-04-12). Verifying on a rendered surface is in [anti-slop-ui SKILL.md](https://github.com/Quegenx/agent-skills/blob/main/skills/anti-ai-slop/anti-slop-ui/SKILL.md).

13. **Check the browser targets before writing CSS** [css-modern-features, SKILL.md]: read `"browserslist"` in `package.json`, then `.browserslistrc`, then the framework's config (Vite and Astro `build.target`); "Use `npx browserslist` in the project root to get the exact resolved list when uncertain." Then compare each feature's status in its topic file. The source's Next.js step ("check `next.config.*` for `browserslist` or `transpilePackages`") is wrong: Next.js reads Browserslist from `package.json`, and with none it uses `["chrome 111", "edge 111", "firefox 111", "safari 16.4"]` ([Next.js: Supported Browsers](https://nextjs.org/docs/architecture/supported-browsers)), so in such an app anything newer needs rule 3's fallback. The source also maps targets onto five tiers and calls its Tier 2 (Chrome 111, Safari 16.4, Firefox 113) "Modern baseline - most features safe", yet that floor lacks `:has()` (Firefox 121), `@starting-style` (Safari 17.5, Firefox 129) and scroll-driven animation (no Firefox release), which its checklist rates Tier 2 (web-features). Use MDN's Baseline instead, which Browserslist takes as a query: `baseline widely available`, `baseline newly available`, `baseline 2024` ([browserslist](https://github.com/browserslist/browserslist)).

## Prove it

Modal dialogs and shown popovers use the top layer without a `z-index` fight; modeless dialogs follow the [separate behavior](POPOVER-AND-DIALOG.md#use-them-and-the-top-layer), and dismissal follows the chosen mode with a working close control; each enter and exit transition can be interrupted mid-way, and in a browser without `overlay` or `interpolate-size` the element still shows and hides, just without the motion; with reduced motion on, nothing moves that isn't essential; every scroller meets the four essentials; each feature used is in a browser the project's Browserslist targets ship it in, or sits behind `@supports` or a cascade fallback that leaves a working page; the page reads in light, dark and forced colors; on a touch phone the native select appears; any code a model wrote has been checked in the browser for the weak spots in rule 12; and every topic file's own Prove it checks pass for the parts you touched.

