# CSS

> Modern CSS rules - mobile-first layout, cascade layers, container queries, modern selectors, animations, logical properties, form controls, anchor positioning

- Skill: `14bryanespinoza/css` (Agent Skill)
- Install (CLI): `npx skillmds@latest add 14bryanespinoza/css`
- Raw SKILL.md: https://api.skillmd.com/api/skills/14bryanespinoza/css/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Marketing & Growth
- Author: 14BryanEspinoza (https://skillmd.com/u/14bryanespinoza)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/14bryanespinoza/css

---


# CSS - Rules and Conventions

---

## 1. Philosophy

1. **Mobile-first** — Base styles target mobile; larger screens get
   progressive enhancements via `min-width` queries, never the reverse.
2. **Baseline-first** — Prefer features labeled Baseline on MDN;
   non-Baseline features require an `@supports` guard plus a fallback.
3. **Performance by default** — Animate only compositable properties
   (`transform`, `opacity`). Animating layout properties causes reflow
   and is prohibited.
4. **Maintainability over convenience** — Custom properties for tokens,
   BEM for naming, cascade layers for priority. Zero magic numbers.
5. **Do no harm to accessibility** — Styling never reduces usability.
   Focus, contrast and reduced-motion policy live in the
   [Accessibility skill](../accessibility/SKILL.md): reference it, do
   not duplicate it.

## 2. Minimum Versions

| Technology | Version        |
| ---------- | -------------- |
| CSS        | CSS3+ (modern) |

Baseline-first policy: "Baseline YYYY" features are safe to use;
experimental ones require an `@supports` guard plus fallback.

## 3. Mobile-First

**When:** writing any component or page style.
**Rule:** author base styles for mobile, then enhance with `(width >=)`
range-syntax queries (Baseline 2023; replaces the `767.98px` hack).
**Why:** mobile constraints produce leaner defaults; desktop overrides
are additive instead of destructive.

```css
/* Base: mobile */
.container {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}

@media (width >= 768px) {
  .container {
    grid-template-columns: repeat(2, 1fr);
  }
} /* tablet */
@media (width >= 1024px) {
  .container {
    grid-template-columns: repeat(3, 1fr);
  }
} /* desktop */
/* Band query: @media (768px <= width < 1024px) */
```

| Breakpoint    | Min width | Usage          |
| ------------- | --------- | -------------- |
| Base (mobile) | 0px       | Smartphones    |
| Tablet        | 768px     | Tablets        |
| Desktop       | 1024px    | Desktops       |
| Large screen  | 1280px    | Large desktops |

## 4. Layout

### Flexbox (one dimension)

**When:** navigation bars, toolbars, centering, rows of items.
**Rule:** Flexbox for single-axis distribution; switch to Grid when
alignment needs both axes.
**Why:** each system solves its own axis problem cleanly.

```css
.navbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.centered {
  display: flex;
  align-items: center;
  justify-content: center;
}
.card-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}
.card-row > * {
  flex: 1 1 300px;
} /* wraps without media queries */
```

Controls: `flex-direction`, `flex-wrap`, `flex`, `align-items`,
`justify-content`, `gap`.

### Grid (two dimensions)

**When:** page layouts, card grids, alignment spanning rows and columns.
**Rule:** define tracks with `repeat(auto-fill, minmax(...))` so items
respond without breakpoints; named areas only on full page shells.
**Why:** intrinsic tracks remove most layout media queries.

```css
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(250px, 100%), 1fr));
  gap: 1.5rem;
}

/* Sidebar + content (mobile-first) */
.layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
}
@media (width >= 768px) {
  .layout {
    grid-template-columns: 250px 1fr;
  }
}
.card {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
}
```

Controls: `grid-template-columns/rows`, `grid-column: 1 / -1`,
`grid-row: span N`, `place-items`, `gap`.

### Anchor Positioning (Baseline 2026)

**When:** tooltips, menus, popovers positioned relative to a trigger.
**Rule:** use `anchor-name` + `position-anchor` + `position-area`;
wrap in `@supports` with a plain fallback position.
**Why:** replaces JS positioning libraries; the fallback keeps older
browsers functional.

```css
.trigger {
  anchor-name: --tip-anchor;
}

.tooltip {
  position: fixed;
  top: 0; /* fallback */
  @supports (position-area: top) {
    top: revert;
    position-anchor: --tip-anchor;
    position-area: top;
    position-try-fallbacks: flip-block; /* flip on overflow */
    margin-bottom: 8px;
  }
}
```

## 5. Naming — BEM Only

**When:** naming any class.
**Rule:** `block__element--modifier`. Blocks are independent components,
elements are parts (`__`), modifiers are variations (`--`).
**Why:** flat, predictable specificity and greppable names eliminate
specificity wars without `!important`.

```css
.card {
  border: 1px solid var(--color-border);
  padding: var(--space-4);
}
.card--featured {
  border-color: var(--color-primary);
}
.card__title {
  font-size: var(--text-lg);
  font-weight: 600;
}
.card__title--large {
  font-size: var(--text-xl);
}
.card__button--primary {
  background: var(--color-primary);
}
```

Utility classes belong in the `utilities` layer only (section 8). A full
utility-first system is owned by [Tailwind CSS](../tailwindcss/SKILL.md).

## 6. Custom Properties

**When:** any repeated value — colors, spacing, radii, shadows, motion,
z-index.
**Rule:** declare design tokens on `:root`; consume them everywhere with
`var()`. Hardcoded values are prohibited.
**Why:** theming and dark mode become variable swaps instead of
find-and-replace campaigns.

```css
:root {
  --color-primary: #6366f1;
  --color-danger: #ef4444;
  --color-text: #111827;
  --color-bg: #ffffff;
  --color-border: #e5e7eb;
  --font-sans: system-ui, sans-serif;

  --space-2: 0.5rem;
  --space-4: 1rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --radius-md: 0.5rem;
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
  --transition-fast: 150ms ease;
  --z-modal: 200;
}

.button {
  background: var(--color-primary);
  padding: var(--space-2) var(--space-4);
  transition: background-color var(--transition-fast);
}
/* Scale tokens to project needs; these categories are the minimum */
```

### Theming with Data Attributes

Override tokens per theme; components never change:

```css
[data-theme="dark"] {
  --color-bg: #111827;
  --color-text: #f9fafb;
}
```

### @property — Typed Variables (Baseline 2024)

Register a type when a custom property needs an initial value,
inheritance control or transitionability:

```css
@property --hue {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
.color-wheel {
  background: hsl(var(--hue) 80% 50%);
  transition: --hue 0.3s;
}
```

## 7. Native Nesting

**When:** styling a component subtree (Baseline 2023).
**Rule:** nest with `&` up to three levels deep; deeper nesting means
the component should be split.
**Why:** keeps related styles together without preprocessor build steps.

```css
.card {
  border: 1px solid var(--color-border);
  padding: var(--space-4);

  & .card__title {
    font-size: var(--text-lg);
  }
  &:hover {
    box-shadow: var(--shadow-md);
  }
  @media (width >= 768px) {
    padding: var(--space-6);
  }
}
```

## 8. Cascade Layers

**When:** structuring any stylesheet.
**Rule:** declare the layer order first, then place code in
`reset` → `base` → `components` → `utilities`. Later layers win over
earlier ones regardless of specificity.
**Why:** gives priority an explicit home instead of leaking it into
selectors and `!important`.

```css
@layer reset, base, components, utilities;

/* Surgical reset: no global margin/padding wipe */
@layer reset {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }
  body {
    margin: 0;
    line-height: 1.5;
  }
  img,
  picture,
  video,
  svg {
    display: block;
    max-width: 100%;
  }
  input,
  button,
  textarea,
  select {
    font: inherit;
  }
}

@layer base {
  body {
    font-family: var(--font-sans);
    color: var(--color-text);
    background: var(--color-bg);
  }
}
@layer components {
  .card {
    border: 1px solid var(--color-border);
  }
}
/* Utilities: highest priority; !important acceptable here */
@layer utilities {
  .mt-4 {
    margin-top: var(--space-4);
  }
  .hidden {
    display: none !important;
  }
}
```

**Priority order:** `utilities` > `components` > `base` > `reset`.
Layered styles always lose to unlayered styles — keep third-party
overrides unlayered deliberately.

Do NOT use a universal `* { margin: 0; padding: 0 }`: it wipes useful
spacing and leaks into third-party widgets.

`@import` is allowed only to assign layers:
`@import url("reset.css") layer(reset);`

## 9. Container Queries

**When:** a component must adapt to its container, not the viewport.
**Rule:** set `container-type: inline-size` on the wrapper, query it
with `@container`. Prefer container queries over viewport queries for
reusable components.
**Why:** the same card renders correctly in a sidebar, modal or full
page with zero duplicated variants.

```css
.card-wrapper {
  container: card / inline-size;
}

@container card (width >= 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
  }
}

/* Style query (Baseline newly available 2026) */
@container card style(--featured: true) {
  .card {
    border-color: var(--color-primary);
  }
}
```

Size queries are Baseline 2023; style queries are newly Baseline — gate
them behind a browser-support check until browserslist covers them.

## 10. Modern Selectors

### :has() — Parent Selector (Baseline 2023)

```css
.card:has(img) {
  grid-template-rows: auto 1fr;
}
.form-group:has(:invalid) {
  border-color: var(--color-danger);
}
tr:has(input:checked) {
  background: var(--color-bg-secondary);
}
```

### Specificity Control — :where() and :is()

Use `:where()` for zero-specificity defaults; `:is()` takes the
specificity of its most specific argument — know which you need:

```css
:where(.card, .panel) {
  margin: 0;
} /* (0,0,0): easy to override */
:is(.card, #header) {
  color: red;
} /* (1,0,0): because of #header */
.button:not(.button--primary) {
  background: var(--color-bg);
}
```

### Keyboard Focus

Never delete the outline without a visible replacement. Full focus
policy lives in the [Accessibility skill](../accessibility/SKILL.md):

```css
.button:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.form-group:focus-within {
  border-color: var(--color-primary);
}
```

### @scope — Style Scoping (Baseline 2025)

Limit a style block to a DOM subtree; lowers specificity and prevents
leaks:

```css
@scope (.card) to (.nested) {
  .title {
    font-size: var(--text-lg);
  } /* stays inside .card */
}
```

### :popover-open (Baseline 2024)

Complements the HTML Popover API ([HTML](../html/SKILL.md)):

```css
.menu[popover] {
  display: none;
}
.menu:popover-open {
  display: block;
}
.menu::backdrop {
  background: rgb(0 0 0 / 0.4);
}
```

## 11. Modern Functions

**Rule:** `clamp()` for fluid bounds, `min()`/`max()` for caps and
floors, `calc()` for mixed-unit math — instead of breakpoint duplication
wherever a continuous value works.

```css
font-size: clamp(1rem, 0.75rem + 0.5vw, 1.125rem);
width: min(100%, 1200px);
padding: max(1rem, 2vw);
grid-template-columns: repeat(auto-fill, minmax(min(250px, 100%), 1fr));
height: calc(100vh - var(--header-height));
```

## 12. @supports Feature Queries

Guard every non-Baseline feature. Key guards used by this skill:

| Feature            | Guard condition                      |
| ------------------ | ------------------------------------ |
| Grid               | `(display: grid)`                    |
| Container queries  | `(container-type: inline-size)`      |
| Native nesting     | `(selector(&))`                      |
| Anchor positioning | `(position-area: top)`               |
| `field-sizing`     | `(field-sizing: content)`            |
| `interpolate-size` | `(interpolate-size: allow-keywords)` |

Combine conditions with `and` / `or` / `not`, e.g.
`@supports (display: grid) and (container-type: inline-size)`.

## 13. Typography

### Fluid Scale

Define headings once with `clamp()`. `text-wrap: balance` prevents
orphaned words; `pretty` avoids single-word paragraph last lines.

```css
body {
  font-family: var(--font-sans);
  line-height: 1.6;
}
h1 {
  font-size: clamp(1.75rem, 1rem + 2vw, 2.5rem);
  line-height: 1.2;
}
h2 {
  font-size: clamp(1.5rem, 1rem + 1.5vw, 2rem);
}
h3 {
  font-size: clamp(1.25rem, 1rem + 0.5vw, 1.5rem);
}
h1,
h2,
h3 {
  text-wrap: balance;
}
p {
  max-width: 70ch;
  text-wrap: pretty;
}
```

### Leading Trim

`text-box-trim` removes glyph-leading space for pixel-perfect headings
(Chrome/Edge 133+, Safari 18.2+; verify Firefox status on MDN):

```css
@supports (text-box-trim: trim-both) {
  h1,
  h2,
  h3 {
    text-box-trim: trim-both;
    text-box-edge: cap alphabetic;
  }
}
```

### @font-face — Variable Fonts

```css
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter.woff2") format("woff2");
  font-weight: 400 700; /* variable range */
  font-display: swap; /* no invisible text during load */
  unicode-range: U+0000-00FF;
}
```

Declare only variation axes the foundry documents.
`-webkit-font-smoothing` is non-standard macOS-only polish — never rely
on it.

## 14. Animations and Transitions

CSS owns animation. JavaScript enters only when CSS cannot express the
behavior (physics, canvas, gesture sync — see the
[JavaScript skill](../javascript/SKILL.md)).

**Rule:** animate `transform` and `opacity` only; everything else
(`width`, `top`, `margin`) forces layout every frame.
**Why:** compositor-only animation runs off the main thread and
protects INP.

### Transitions

Prefer transitions over keyframes whenever a state pair exists:

```css
.button {
  transition:
    background-color 150ms ease,
    transform 150ms ease;
}
.button:hover {
  background-color: var(--color-primary-hover);
  transform: translateY(-1px);
}
```

### Keyframes

Reserve `@keyframes` for multi-step or looping animation. Shorthand:
`<name> <duration> <timing> <delay> <iteration> <direction>
<fill-mode>`.

```css
.fade-up {
  animation: fadeUp 300ms ease-out both;
}

@keyframes fadeUp {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
```

### Enter/Exit (@starting-style + allow-discrete)

Animate elements toggling `display: none` (modals, popovers, menus).
`@starting-style` defines the entry state; `allow-discrete` lets
`display`/`overlay` transition so exit animation completes before
removal.

```css
.menu {
  transition:
    opacity 150ms ease,
    display 150ms allow-discrete,
    overlay 150ms allow-discrete;
  @starting-style {
    opacity: 0;
    translate: 0 -10px;
  }
}
.menu[hidden] {
  opacity: 0;
}
```

### Intrinsic Size — interpolate-size (NOT Baseline)

Enables `height: auto` transitions. Experimental, Chromium-first —
guard it and accept the fixed-height fallback:

```css
@supports (interpolate-size: allow-keywords) {
  :root {
    interpolate-size: allow-keywords;
  }
}
.accordion-panel {
  overflow: hidden;
  height: 0;
  transition: height 200ms ease;
}
.accordion-panel.open {
  height: auto;
}
```

### View Transitions

Same-document transitions smooth SPA state changes. Guard with
`document.startViewTransition` in JS before styling:

```css
::view-transition-old(root) {
  animation: 300ms ease fadeOut;
}
::view-transition-new(root) {
  animation: 300ms ease fadeIn;
}
```

### Reduced Motion (mandatory)

Ship this with every animation; full policy lives in the
[Accessibility skill](../accessibility/SKILL.md):

```css
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
```

Scroll-driven timelines remain experimental — not for production.

## 15. Responsive Media

```css
img,
video {
  max-width: 100%;
  height: auto;
  display: block;
}

.media-cover {
  width: 100%;
  height: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.container {
  width: 100%;
  max-width: 1200px;
  margin-inline: auto;
  padding-inline: var(--space-4);
}
```

`aspect-ratio` reserves space (protects CLS); `object-fit: cover` crops
without distortion, `contain` letterboxes instead. Markup-level
responsive images belong to the [HTML skill](../html/SKILL.md).

## 16. Logical Properties

**Rule:** use logical properties for anything direction-dependent.
**Why:** RTL/LTR layouts adapt automatically instead of doubling
selectors under `[dir="rtl"]`.

| Physical             | Use instead                  |
| -------------------- | ---------------------------- |
| `margin-left/right`  | `margin-inline`              |
| `padding-top/bottom` | `padding-block`              |
| `border-left`        | `border-inline-start`        |
| `width` / `height`   | `inline-size` / `block-size` |
| `left/right` insets  | `inset-inline`               |
| `top/bottom` insets  | `inset-block`                |
| `text-align: left`   | `text-align: start`          |

```css
.element {
  margin-inline: var(--space-4);
  padding-block: var(--space-2);
  border-inline-start: 1px solid var(--color-border);
  inline-size: 100%;
}
```

## 17. Dark Mode and System Preferences

**Rule:** declare `color-scheme` first — it enables `light-dark()` and
correct UA chrome (scrollbars, form controls):

```css
:root {
  color-scheme: light dark; /* required for light-dark() */
  --bg: light-dark(#ffffff, #1a1a1a);
  --text: light-dark(#000000, #ffffff);
}
```

Pair with the HTML meta `<meta name="color-scheme" content="light dark">`.
Force a theme by swapping tokens via `data-theme` (section 6).

### System Preference Queries

```css
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
  }
}
@media (prefers-contrast: high) {
  :root {
    --color-text: #000000;
    --color-border: #000000;
  }
}
@media (prefers-reduced-transparency: reduce) {
  .modal-backdrop {
    background: rgb(0 0 0 / 0.8);
  }
}
/* Experimental: near-zero support today */
@media (prefers-reduced-data: reduce) {
  .hero {
    background-image: none;
  }
}
```

`light-dark()` returns the light value unless `color-scheme` is set —
declare it or the function silently does nothing useful.

## 18. Scroll

```css
html {
  scroll-behavior: smooth;
}

/* Carousel: snap horizontally, stop at each item */
.scroll-container {
  scroll-snap-type: x mandatory;
  overflow-x: auto;
  display: flex;
  gap: 1rem;
}
.scroll-item {
  scroll-snap-align: start;
  scroll-snap-stop: always;
  flex: 0 0 100%;
}

.modal-content {
  overscroll-behavior: contain;
} /* modal chaining */

.app-shell {
  scrollbar-gutter: stable;
} /* no shift on overflow */
```

Never put `overscroll-behavior: none` on `body` — it breaks mobile
scroll chaining; use `contain` on inner containers. Do not restyle
scrollbars with `::-webkit-scrollbar`; prefer `scrollbar-width` /
`scrollbar-color`.

## 19. Cascade and Specificity

**Rule:** style exclusively with classes. IDs, inline styles and
`!important` are banned except inside the utilities layer.
**Why:** class-level specificity keeps every conflict resolvable by
source order and layers.

```css
/* ❌ */
#header {
}
/* ✅ */
.site-header {
}

/* Acceptable ONLY inside @layer utilities */
.hidden {
  display: none !important;
}
```

Layer order beats specificity; unlayered beats layered. Fix conflicts
by moving code to the right layer, not by raising specificity.

## 20. CSS Performance

HTML-layer rules only. Measurement, budgets and loading strategy live in
the [Performance skill](../performance/SKILL.md).

```css
/* Skip rendering offscreen sections */
.section-below-fold {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px; /* estimate prevents CLS */
}
.widget {
  contain: layout style paint;
} /* isolate subtree re-render */
```

**will-change discipline:** apply only on elements with a measured
animation problem, remove after; every hint allocates memory.

## 21. Form Controls

Ownership split: HTML owns structure, JavaScript owns behavior and
validation logic, CSS owns **native styling only**
([HTML](../html/SKILL.md), [JavaScript](../javascript/SKILL.md)).

```css
input[type="checkbox"],
input[type="radio"],
input[type="range"],
progress {
  accent-color: var(--color-primary); /* native accent */
}

input:user-invalid {
  border-color: var(--color-danger);
} /* Baseline 2023 */

/* Controls grow with content (newly Baseline 2026 — guard it) */
@supports (field-sizing: content) {
  textarea {
    field-sizing: content;
    min-height: 2lh;
    max-height: 10lh;
  }
}
```

With `field-sizing`, never combine fixed `width`/`height` — constrain
with `min-*`/`max-*`. For custom-styled controls start from
`appearance: none` plus `font: inherit` (reset, section 8). Error
announcement (`role="alert"`, `aria-invalid`) is owned by
[Accessibility](../accessibility/SKILL.md).

## 22. Methodology

Before using any property, function or pattern not documented in this
skill:

1. **MCP Context7 (priority)** — query library/spec docs.
2. **MDN Web Docs** — existence, Baseline status, browser support.
3. **Can I Use** — verify against the project's browserslist targets.
4. **Official spec** — CSS WG drafts for edge cases.

**Hard rule:** if it is neither in this skill nor verifiable against two
authoritative sources, DO NOT USE IT. Document it as an assumption or
risk to the orchestrator.

## 23. Prohibitions

- No `!important` outside `@layer utilities` or a documented exception
- No IDs, inline styles or `style="..."` attributes for styling
- No nesting deeper than 3 levels — split the component
- No `px` typography — use `rem` / `clamp()`
- No hardcoded colors or magic numbers — use custom properties
- No `float` or absolute positioning for general layout
- No animating `width`, `height`, `top`, `left`, `margin`
- No universal `* { margin: 0; padding: 0 }` resets
- No images without `max-width: 100%`
- No `@import` in CSS except `layer()` assignment
- No vendor prefixes on Baseline properties (e.g. `-webkit-backdrop-filter`)
- No `will-change` spraying; measured problems only
- No global `overscroll-behavior: none` on `body`
- No `::-webkit-scrollbar` custom styling
- No `font-variation-settings` axes the font does not declare
- No decorative infinite animations without a reduced-motion escape
- No re-implementing rules owned by sibling skills — reference them

## 24. References

> Structure and forms markup: [HTML](../html/SKILL.md)
> Behavior and validation logic: [JavaScript](../javascript/SKILL.md)
> WCAG, focus, reduced motion: [Accessibility](../accessibility/SKILL.md)
> Core Web Vitals and budgets: [Performance](../performance/SKILL.md)
> CSP and XSS: [Security](../security/SKILL.md)
> Utility-first framework: [Tailwind CSS](../tailwindcss/SKILL.md)

Visual effects (filters, clip-path, masks, 3D transforms, colors beyond
`light-dark()`) were removed — candidate future `visual-effects` skill.

Last updated: 2026-08

