
Css
- 4 installs
- 19 repo stars
- Updated August 1, 2026
- xobotyi/cc-foundry
Helps with frontend development tasks during AI-assisted development.
About
css is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted coding.
- css
- Frontend Development
- AI-coding skill
Css by the numbers
- 4 all-time installs (skills.sh)
- Ranked #1,817 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/xobotyi/cc-foundry --skill cssAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 19 |
| Last updated | August 1, 2026 |
| Repository | xobotyi/cc-foundry ↗ |
What it does
Helps with frontend development tasks during AI-assisted development.
Files
CSS
Predictability is the highest CSS virtue. If your styles require `!important` to work, restructure the cascade.
CSS rewards explicit, low-specificity selectors and intentional cascade ordering. Prefer boring, readable patterns over clever one-liners.
References
| Topic | Reference | Contents |
|---|---|---|
| Layout | [${CLAUDE_SKILL_DIR}/references/layout.md] | Flex shorthand values, grid details (subgrid, implicit rows, alignment), layout patterns |
| Modern CSS | [${CLAUDE_SKILL_DIR}/references/modern-css.md] | Extended modern CSS patterns and examples |
| SCSS | [${CLAUDE_SKILL_DIR}/references/scss.md] | @forward patterns, module configuration, built-in modules, file organization |
| Responsive | [${CLAUDE_SKILL_DIR}/references/responsive.md] | Extended responsive design patterns and examples |
| Methodologies | [${CLAUDE_SKILL_DIR}/references/methodologies.md] | Methodology patterns and architecture details |
Selectors and Specificity
- Single class selectors by default — keep specificity flat at 0-1-0
- Never use ID selectors for styling — IDs are for anchors and JS hooks
- Never qualify classes with elements —
.errornotdiv.error - Max nesting depth: 3 levels — deeper nesting couples CSS to DOM structure
- Avoid
!important— use cascade layers or restructure selectors instead;
only valid use is in a low-priority reset layer for truly essential styles
- Use
:where()to zero-out specificity when needed —
:where(.card) .title has 0-0-1 specificity
- Use
:is()with awareness — it takes the highest specificity of its arguments - Flatten nested selectors in SCSS — ability to nest does not mean you should
Layout Systems
Choosing Flexbox vs Grid
| Use Case | System |
|---|---|
| One-dimensional flow (row or column) | Flexbox |
| Two-dimensional layout (rows AND columns) | Grid |
| Content-driven sizing | Flexbox |
| Layout-driven sizing | Grid |
| Component internals (nav items, card content) | Flexbox |
| Page-level structure, complex arrangements | Grid |
| Items need to wrap naturally | Flexbox |
| Precise placement on named lines/areas | Grid |
Both work together — a grid item can be a flex container and vice versa.
Flexbox
- Always use the
flexshorthand — it sets intelligent defaults.
See ${CLAUDE_SKILL_DIR}/references/layout.md for the full shorthand value table
flex-flow: row wrapcombinesflex-directionandflex-wrap- Use
flex-wrapwith aflexbasis for responsive layouts without media queries:
flex: 1 1 300px wraps items when they can't maintain 300px minimum
- Centering:
display: flex; align-items: center; justify-content: center
or margin: auto on a flex child
gapover margin hacks — works in both flexbox and grid- Avoid
justify-content: space-betweenwith wrap — causes orphan gaps;
prefer gap + flex-wrap
CSS Grid
repeat(auto-fit, minmax(250px, 1fr))is the canonical responsive grid — no media
queries needed
- Prefer
auto-fitoverauto-fill—auto-fitexpands columns to fill space;
auto-fill keeps empty tracks
- Use named grid areas for page-level layouts — they auto-create named lines
- Never hardcode
pxwidths on grid items — usefr,minmax(), orauto grid-auto-flow: densefills visual holes — use carefully, it breaks
visual/source order alignment (a11y concern)
- Never use
orderin ways that break logical reading order - See
${CLAUDE_SKILL_DIR}/references/layout.mdfor subgrid, implicit rows,
alignment shorthands, and negative line numbers
General Layout Rules
- Never use
floatfor layout — floats are for wrapping text around images - Intrinsic sizing first — use
flex-wrap,min(),max(),clamp()before
reaching for media queries
gapover margin hacks in both flexbox and grid
CSS Nesting
- Use
&for pseudo-classes/elements and compound selectors —
&:hover, &::before, &.active
- Omit
&for descendant selectors —.card { .title {} }works &is required when the nested selector starts with a type selector —
& p {} not p {}
- Nesting at-rules (
@media,@supports,@container) nest directly inside rules - Specificity:
:is()wrapping applies in nesting — be aware that specificity
may differ from the equivalent unnested selector
- Max depth: 3 levels — same rule as flat CSS
Cascade Layers (@layer)
- Declare all layers at the top of the stylesheet in a single statement:
@layer reset, defaults, themes, components, utilities;
- First declared = lowest priority; un-layered styles always beat layered styles
!importantreverses layer order —!importantin the lowest layer wins over
!important in higher layers
- Import third-party CSS into sub-layers:
@import url('vendor.css') layer(vendor.bootstrap);
- Use
revert-layerto roll back to the previous layer's value !importantin low layers is intentional — it means "this style is essential,
don't override"
- Don't create layers per-component — layers manage cascade priority between
categories (reset vs component vs utility), not scope
- Nested layers:
@layer components { @layer buttons, cards; }—
access via @layer components.buttons
- Anonymous layers (
@layer { }) can't be appended to later
Container Queries
- Define containment:
container-type: inline-sizeon the wrapper - Name containers for targeting:
container: card / inline-size - Query by name:
@container card (width > 400px) { } - Unnamed queries hit the nearest ancestor container
Container Query Units
| Unit | Meaning |
|---|---|
cqw / cqh | 1% of container width / height |
cqi / cqb | 1% of container inline / block size |
cqmin / cqmax | Smaller / larger of cqi or cqb |
Use cqi instead of vw for container-scoped fluid values: font-size: clamp(1rem, 2.5cqi + 0.5rem, 2rem)
Responsive Design
Responsive Hierarchy
Design from the inside out — use the right tool for each level:
| Level | Tool | When |
|---|---|---|
| Content-driven | Flexbox wrapping, min()/max()/clamp() | Always — baseline |
| Container-driven | Container queries, cqi/cqw units | Component adapts to parent |
| Viewport-driven | Media queries, vw/vh/dvh | Page-level layout changes |
| User preference | prefers-* media queries | Color scheme, motion, contrast |
Core Rules
- Mobile-first — default styles for small screens, enhance upward
- Content-driven breakpoints — let content decide, not device sizes
remfor breakpoints:@media (width >= 45rem)not(min-width: 768px)- Use modern range syntax:
@media (768px <= width < 1024px) - Logical properties for layout:
margin-inline-startnotmargin-left - Container queries for component-level adaptation; media queries only for
viewport-dependent elements (nav, header)
- Respect user preferences:
prefers-reduced-motion,prefers-color-scheme,
prefers-contrast
- Single container max-width pattern: `width: min(100% - 2rem, 75rem);
margin-inline: auto — avoid multiple max-width` values at different breakpoints
Fluid Sizing
clamp(min, preferred, max)for fonts, spacing, and container widths- Build a fluid type scale with custom properties:
--step-0: clamp(1rem, 0.5rem + 1.5vw, 1.25rem)
- Never use
vwalone for font size — it blows up on large screens;
always pair with clamp() and rem
- Use
cqiinstead ofvwfor container-scoped fluid values
Logical Properties
Use logical properties for layout-sensitive values (margins, padding, borders, text alignment, positioning offsets). Physical properties are fine for visual effects not affected by writing direction (e.g., box-shadow offsets).
| Physical | Logical |
|---|---|
left / right | inline-start / inline-end |
top / bottom | block-start / block-end |
width / height | inline-size / block-size |
margin-left | margin-inline-start |
padding-top | padding-block-start |
text-align: left | text-align: start |
Shorthands: margin-block: 1rem 2rem (block-start, block-end); margin-inline: auto (both inline directions).
User Preference Queries
- Color scheme: declare
color-scheme: light darkon:rootand use
@media (prefers-color-scheme: dark) for overrides
- Reduced motion: either remove motion in
prefers-reduced-motion: reduceor
add motion only in prefers-reduced-motion: no-preference (progressive enhancement approach)
- High contrast:
@media (prefers-contrast: more) - Interaction:
@media (hover: hover)for hover effects on capable devices;
@media (pointer: coarse) for touch targets (min 44px)
Responsive Images
img { max-width: 100%; height: auto; display: block; }- Use
aspect-ratio+object-fit: coverfor hero images
The :has() Selector
Select elements based on descendants or siblings — the "parent selector."
- Anchor to specific elements, not
body,:root, or*— broad anchors
force expensive re-evaluation on every DOM change
- Use direct child (
>) or sibling (+,~) combinators inside:has()
to limit traversal scope
- Cannot nest
:has()inside:has() - Pseudo-elements are not valid inside
:has() .layout:has(> .sidebar-open)(good) notbody:has(.sidebar-open)(bad)
Custom Properties
- Define design tokens on
:root— scope overrides to components - Semantic naming:
--color-text-primarynot--dark-gray - Use kebab-case; prefix with category:
--color-,--spacing-,--font- - Provide fallbacks for component-level variables:
var(--button-bg, var(--color-primary))
- Custom properties are case-sensitive —
--my-colordiffers from--My-Color - Custom properties inherit by default (unlike most CSS properties)
- Use
@propertyfor typed, animatable custom properties — enables type checking
(invalid values fall back to initial-value), controlled inheritance (inherits: false), and transitions on custom properties
View Transitions
view-transition-namemust be unique per page at transition time- Keep transitions short — 200-400ms for UI, longer for page-level
- Always respect
prefers-reduced-motion: reducefor view transitions - Same-document (SPA):
document.startViewTransition(() => { /* update DOM */ }) - Cross-document (MPA):
@view-transition { navigation: auto; } - Named transitions target specific elements via
::view-transition-group(name)
Box Model and Sizing
- Always set
box-sizing: border-boxglobally via reset remfor font sizes and breakpoints — respects user preferencesemfor component-relative spacing (padding that scales with font size)- Fluid sizing with
clamp()— replace manual breakpoint ladders aspect-ratioover padding hacks for maintaining proportions- No units on zero values —
margin: 0notmargin: 0px
(except where required: flex: 0 0 0px)
- Leading zero on decimals:
opacity: 0.5notopacity: .5 - Shorthand hex where possible:
#ebcnot#eebbcc
SCSS / Dart Sass
Module System
@useand@forwardonly —@importis deprecated (Dart Sass 1.80.0),
removed in 3.0.0
@usemust appear before any rules except@forward- Namespace defaults to the last component of the URL (without extension)
- Members are scoped to the loading file — not globally available
- Each module loaded exactly once — no duplicate CSS output
- Namespace access:
variables.$primarynot global$primary - No-namespace
@use 'variables' as *— use sparingly, only for own files math.div()for division — the/operator is deprecated- Prefix private members with
-or_ - Prefer mixins over
@extend— more predictable output;@extendproduces
unexpected selectors and doesn't work across media queries
- Max nesting: 3 levels
@forward re-exports modules, supports prefixing and visibility control. Module configuration uses !default variables and @use ... with (). See ${CLAUDE_SKILL_DIR}/references/scss.md for @forward patterns, configuration passthrough, built-in module usage, and file organization conventions.
Migration
Use the official migrator: sass-migrator module --migrate-deps entrypoint.scss. For built-in functions only: sass-migrator module --built-in-only entrypoint.scss.
CSS Methodologies
BEM (Block Element Modifier)
- Blocks are standalone components:
.card,.nav,.form - Elements are parts of a block (double underscore):
.card__title,.card__image - Modifiers are variations (double hyphen):
.card--featured,.card__title--bold - Never nest elements:
.card__header__titleis wrong — flatten to.card__title
or create a new block
- Modifiers don't exist alone — always pair with base class:
class="card card--featured"
- Use BEM in team projects, large codebases, projects without scoped styles
- Skip BEM when using CSS Modules, utility-first CSS, or small projects
CSS Modules
- Use simple, descriptive class names — scoping eliminates conflict risk
- One module per component
- Compose shared styles:
composes: resetButton from './shared.module.css' - Global escape hatch:
:global(.utility-class)when needed - Pair with custom properties for theming (variables aren't scoped)
Architecture (ITCSS)
Organize styles by specificity, low to high: Settings → Tools → Generic → Elements → Objects → Components → Utilities. Maps naturally to cascade layers: @layer settings, generic, elements, objects, components, utilities;
Formatting
- 2-space indentation, no tabs
- One declaration per line
- Semicolon after every declaration including the last
- Space after colon:
color: rednotcolor:red - Opening brace on same line as selector
- Blank line between rules
- Lowercase everything (selectors, properties, values, hex colors)
- Single quotes for attribute selectors and font names
- Group declarations by category: layout → box model → typography →
visual → interaction
Application
When writing CSS:
- Apply all conventions silently — don't narrate each rule being followed.
- Use intrinsic sizing and fluid techniques before media queries.
- If an existing codebase contradicts a convention, follow the codebase and
flag the divergence once.
When reviewing CSS:
- Cite the specific violation and show the fix inline.
- Don't lecture or quote the rule — state what's wrong and how to fix it.
Bad review comment:
"According to CSS best practices, you should avoid using ID selectors
for styling because they have high specificity."
Good review comment:
"`#header` -> `.header` -- IDs create specificity 1-0-0, difficult to override."Integration
The coding skill governs workflow; this skill governs CSS implementation choices. For SCSS, this single skill covers both CSS and SCSS conventions.
Predictability is the highest CSS virtue. When in doubt, keep specificity low and cascade explicit.
{
"sources": {
"Google HTML/CSS Style Guide": "https://google.github.io/styleguide/htmlcssguide.html",
"MDN - CSS Nesting": "https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting",
"MDN - Cascade Layers (@layer)": "https://developer.mozilla.org/en-US/docs/Web/CSS/@layer",
"MDN - CSS Custom Properties": "https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties",
"MDN - CSS Flexbox Basic Concepts": "https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Basic_concepts_of_flexbox",
"MDN - CSS Grid Layout": "https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout",
"MDN - CSS Container Queries": "https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries",
"MDN - View Transition API": "https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API",
"MDN - :has() Selector": "https://developer.mozilla.org/en-US/docs/Web/CSS/:has",
"CSS-Tricks - Cascade Layers Guide": "https://css-tricks.com/css-cascade-layers/",
"CSS-Tricks - Complete Guide to Flexbox": "https://css-tricks.com/snippets/css/a-guide-to-flexbox/",
"CSS-Tricks - Complete Guide to Grid": "https://css-tricks.com/snippets/css/complete-guide-grid/",
"Sass - @use Documentation": "https://sass-lang.com/documentation/at-rules/use/",
"Sass - @forward Documentation": "https://sass-lang.com/documentation/at-rules/forward/",
"Sass - Breaking Change: @import Deprecation": "https://sass-lang.com/documentation/breaking-changes/import/",
"Responsive Design Guide (Ahmad Shadeed)": "https://ishadeed.com/article/responsive-design/"
},
"lastFetched": "2026-02-16T15:42:59.916Z"
}
Layout Systems
CSS layout with Flexbox and Grid. Choose the right system, apply it correctly.
Flexbox vs Grid
| Use | System |
|---|---|
| One-dimensional flow (row or column) | Flexbox |
| Two-dimensional layout (rows AND columns) | Grid |
| Content-driven sizing | Flexbox |
| Layout-driven sizing | Grid |
| Component internals (nav items, card content) | Flexbox |
| Page-level structure, complex arrangements | Grid |
| Items need to wrap naturally | Flexbox |
| Precise placement on named lines/areas | Grid |
Both work together. A grid item can be a flex container and vice versa.
Flexbox
Container Properties
.container {
display: flex;
flex-direction: row; /* row | row-reverse | column | column-reverse */
flex-wrap: wrap; /* nowrap | wrap | wrap-reverse */
justify-content: flex-start; /* flex-start | flex-end | center | space-between | space-around | space-evenly */
align-items: stretch; /* stretch | flex-start | flex-end | center | baseline */
align-content: normal; /* applies only when flex-wrap: wrap */
gap: 1rem; /* row-gap column-gap */
}Shorthand: flex-flow: row wrap combines flex-direction and flex-wrap.
Item Properties
.item {
flex: 1 1 auto; /* flex-grow flex-shrink flex-basis */
align-self: center;
order: 0;
}Always use the `flex` shorthand. It sets intelligent defaults:
flex: 1=flex: 1 1 0-- equal sizing from zero basisflex: auto=flex: 1 1 auto-- grow/shrink from content sizeflex: none=flex: 0 0 auto-- fully inflexibleflex: initial=flex: 0 1 auto-- can shrink, won't grow
Intrinsic Responsive Wrapping
Use flex-wrap with flex to create responsive layouts without media queries:
/* Items wrap when they can't maintain 300px minimum */
.container {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.item {
flex: 1 1 300px; /* grow, shrink, 300px ideal basis */
}This is the preferred pattern for content-driven responsive layouts.
Centering
/* Perfect centering */
.parent {
display: flex;
align-items: center;
justify-content: center;
}
/* Or with auto margins */
.parent { display: flex; }
.child { margin: auto; }Common Patterns
Space-between with wrapping fallback:
.nav {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 0.5rem;
}Alignment shifting wrapping (title + action):
.header {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.header__title {
flex: 1 1 400px; /* wraps below 400px */
}CSS Grid
Defining Tracks
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto 1fr auto;
gap: 1rem;
}Key functions:
repeat(count, size)-- repeat track patternsminmax(min, max)-- flexible track sizingfit-content(max)-- size to content with a capfrunit -- fraction of remaining free space
Responsive Grid Without Media Queries
The canonical responsive grid pattern:
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}auto-fit-- expand columns to fill space (prefer overauto-fillin most cases)auto-fill-- keep empty tracks (useful when you want consistent column count)
Named Grid Areas
.layout {
display: grid;
grid-template-areas:
"header header header"
"sidebar main aside"
"footer footer footer";
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto 1fr auto;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }Named areas auto-create named lines: header-start, header-end, etc.
Line-Based Placement
.item {
grid-column: 1 / 3; /* start-line / end-line */
grid-row: 2 / span 2; /* start / span count */
}Negative line numbers count from the end: grid-column: 1 / -1 spans full width.
Subgrid
Children inherit parent grid tracks:
.parent {
display: grid;
grid-template-columns: repeat(4, 1fr);
}
.child {
grid-column: 2 / 4;
display: grid;
grid-template-columns: subgrid; /* inherits parent lines */
}Alignment in Grid
.grid {
/* Align all items */
justify-items: start; /* inline axis */
align-items: center; /* block axis */
/* Align the grid itself within container */
justify-content: center;
align-content: start;
}
.item {
/* Override for single item */
justify-self: end;
align-self: stretch;
}
/* Shorthand */
.grid {
place-items: center; /* align-items / justify-items */
place-content: center; /* align-content / justify-content */
}Implicit Grid
Items placed beyond explicit tracks create implicit tracks:
.grid {
grid-auto-rows: minmax(100px, auto); /* size implicit rows */
grid-auto-flow: dense; /* fill holes (use carefully -- affects a11y) */
}Anti-Patterns
| Don't | Do |
|---|---|
float for layout | Flexbox or Grid |
| Flexbox for 2D layouts | Grid |
| Grid for simple row of items | Flexbox |
grid-auto-flow: dense without considering a11y | Explicit placement or accept gaps |
Hardcoded px widths on grid items | fr, minmax(), or auto |
| Media queries for every breakpoint | auto-fit/auto-fill + minmax() |
order that breaks logical reading order | Source order matches visual order |
justify-content: space-between with wrap (orphan gap) | gap + flex-wrap |
CSS Methodologies
Naming conventions, architecture patterns, and organization strategies.
BEM (Block Element Modifier)
Naming convention that creates clear relationships between HTML and CSS.
/* Block */
.card { }
/* Element (double underscore) */
.card__title { }
.card__image { }
/* Modifier (double hyphen) */
.card--featured { }
.card__title--large { }Rules
- Blocks are standalone components:
.card,.nav,.form. - Elements are parts of a block:
.card__header,.card__body. - Modifiers are variations:
.card--dark,.card__title--bold. - Never nest elements:
.card__header__titleis wrong.
Flatten to .card__title or create a new block.
- Modifiers don't exist alone. Always pair with the base class:
class="card card--featured".
When to Use BEM
- Team projects needing consistent conventions
- Large codebases with many components
- Projects without CSS Modules or scoped styles
When BEM Is Unnecessary
- CSS Modules or scoped component styles (framework handles scoping)
- Utility-first CSS (Tailwind)
- Small projects with few components
CSS Modules
Scoped CSS classes compiled to unique identifiers. Eliminates naming conflicts.
/* Button.module.css */
.button {
padding: 0.5rem 1rem;
}
.primary {
background: var(--color-primary);
}import styles from './Button.module.css';
<button className={`${styles.button} ${styles.primary}`}>Click</button>Rules
- Use simple, descriptive class names. Scoping eliminates conflict risk.
.button not .btn-component-primary-v2.
- One module per component.
- Compose shared styles:
.button {
composes: resetButton from './shared.module.css';
}- Global escape hatch:
:global(.utility-class)when needed. - Pair with custom properties for theming (variables aren't scoped).
Utility-First CSS
Small, single-purpose classes composed in HTML. Tailwind CSS is the primary framework.
<div class="flex items-center gap-4 p-4 rounded-lg bg-white shadow-md">
<img class="w-12 h-12 rounded-full" src="avatar.jpg" alt="" />
<div>
<p class="font-semibold text-gray-900">Name</p>
<p class="text-sm text-gray-500">Role</p>
</div>
</div>When to Use Utilities
- Rapid prototyping
- Design system implementation with strict constraints
- Teams comfortable with utility patterns
When to Avoid
- Content-heavy sites with repetitive patterns (use component classes)
- When HTML readability is critical
- Projects without build tooling to purge unused utilities
Hybrid Approach
Combine utilities with component classes:
/* Component base via CSS */
.card {
display: flex;
flex-direction: column;
border-radius: var(--radius-md);
overflow: hidden;
}
/* Variations and one-offs via utilities in HTML */Architecture Patterns
ITCSS (Inverted Triangle CSS)
Organize styles by specificity, from low to high:
1. Settings -- Variables, design tokens 2. Tools -- Mixins, functions 3. Generic -- Reset, normalize 4. Elements -- Bare HTML elements (h1, p, a) 5. Objects -- Layout patterns (.container, .grid) 6. Components -- UI components (.card, .button) 7. Utilities -- Overrides (.hidden, .text-center)
Maps naturally to cascade layers:
@layer settings, generic, elements, objects, components, utilities;Modern Token-Based Architecture
tokens/
├── colors.css /* Design tokens as custom properties */
├── spacing.css
├── typography.css
└── index.css /* @import all tokens */
base/
├── reset.css
├── typography.css
└── index.css
components/
├── button.css
├── card.css
└── index.css
utilities/
├── spacing.css
├── display.css
└── index.cssSelector Strategy
Specificity Management
- Keep specificity flat and low. Prefer single class selectors.
- Never use ID selectors for styling.
#header= specificity 1-0-0. - Avoid qualifying classes with elements.
.errornotdiv.error. - Use `:where()` to zero-out specificity when needed:
:where(.card) .title { /* 0-0-1 specificity */ }- Use `:is()` with awareness -- takes highest specificity of its arguments.
Selector Nesting Depth
- Maximum 3 levels. Deeper nesting = DOM coupling = fragile styles.
- Flatten in SCSS. Just because you can nest doesn't mean you should.
/* Bad */
.page {
.content {
.card {
.card-header {
.title { } /* 5 levels deep */
}
}
}
}
/* Good */
.card__title { } /* flat */Formatting
Declaration Order
Group related properties. Within groups, alphabetize or follow a consistent convention.
Recommended grouping order: 1. Layout (display, position, grid-*, flex-*) 2. Box model (width, height, margin, padding, border) 3. Typography (font-*, text-*, color, line-height) 4. Visual (background, box-shadow, opacity, transform) 5. Interaction (cursor, pointer-events, transition, animation)
General Rules
- 2-space indentation. No tabs.
- One declaration per line.
- Semicolon after every declaration (including the last one).
- Space after colon:
color: rednotcolor:red. - Opening brace on same line as selector.
- Blank line between rules.
- Lowercase everything (selectors, properties, values, hex colors).
- Single quotes for attribute selectors and font names:
font-family: 'Open Sans', sans-serif.
- No units on zero values:
margin: 0notmargin: 0px
(except where required, e.g., flex: 0 0 0px).
- Leading zero on decimals:
opacity: 0.5notopacity: .5. - Shorthand hex where possible:
#ebcnot#eebbcc. - Avoid `!important`. Use cascade layers or specificity to resolve conflicts.
Modern CSS Features
Nesting, cascade layers, container queries, :has(), custom properties, and view transitions.
CSS Nesting
Native CSS nesting eliminates the need for preprocessors in many cases.
.card {
padding: 1rem;
.title {
font-size: 1.5rem;
}
&:hover {
box-shadow: 0 2px 8px rgb(0 0 0 / 0.1);
}
@media (width >= 768px) {
padding: 2rem;
}
}Rules
- Use `&` for pseudo-classes/elements and compound selectors.
&:hover, &::before, &.active.
- Omit `&` for descendant selectors.
.card { .title {} }works. - `&` is required when the nested selector starts with a type selector.
& p {} not p {} (without &, starts a new rule).
- Nesting at-rules works.
@media,@supports,@containernest directly. - Specificity:
:is()wrapping applies..card { .title {} }has same
specificity as .card .title, but nested :is(.card) .title specificity may differ. Be aware of specificity changes.
- Max depth: 3 levels. Deeper nesting creates specificity issues and
couples CSS to DOM structure.
Cascade Layers (@layer)
Layers give explicit control over cascade priority without specificity hacks.
Layer Order
/* Declare order up-front -- first declared = lowest priority */
@layer reset, defaults, components, utilities;
/* Un-layered styles always beat layered styles */Priority (lowest to highest): 1. reset layer 2. defaults layer 3. components layer 4. utilities layer 5. Un-layered styles (highest normal priority)
`!important` reverses layer order: 1. !important reset (highest important priority) 2. !important defaults 3. !important components 4. !important utilities 5. !important un-layered
Syntax
/* Declare order */
@layer reset, defaults, components, utilities;
/* Block rule -- add styles to a layer */
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
}
/* Import into a layer */
@import url('vendor.css') layer(defaults);
/* Nested layers */
@layer components {
@layer buttons, cards;
}
/* Access nested: */
@layer components.buttons { /* ... */ }
/* Anonymous layer (can't be appended to later) */
@layer { /* ... */ }Best Practices
- Declare all layers at the top of the stylesheet in a single statement.
- Typical ordering:
reset, defaults, themes, components, utilities. - Import third-party CSS into sub-layers:
@import url('bootstrap.css') layer(vendor.bootstrap);
- Use `revert-layer` to roll back to the previous layer's value.
- `!important` in low layers is intentional -- it means "this style is
essential, don't override."
- Don't create layers per-component. Layers manage cascade priority
between categories (reset vs component vs utility), not scope.
Container Queries
Style components based on their container's size, not the viewport.
Setup
/* Define containment context */
.card-wrapper {
container-type: inline-size; /* query inline dimension */
container-name: card; /* optional: name for targeting */
}
/* Shorthand */
.card-wrapper {
container: card / inline-size;
}
/* Query the container */
@container card (width > 400px) {
.card { flex-direction: row; }
}
/* Query nearest ancestor (no name) */
@container (width > 600px) {
.card__title { font-size: 1.5rem; }
}Container Query Units
| Unit | Meaning |
|---|---|
cqw | 1% of container width |
cqh | 1% of container height |
cqi | 1% of container inline size |
cqb | 1% of container block size |
cqmin | smaller of cqi or cqb |
cqmax | larger of cqi or cqb |
/* Fluid font size based on container, not viewport */
.card__title {
font-size: clamp(1rem, 2.5cqi + 0.5rem, 2rem);
}When to Use
- Container queries: Components that appear in different-width contexts
(cards in sidebar vs main content, widgets in dashboards).
- Media queries: Viewport-dependent elements (site header, navigation,
full-width sections).
- Flexbox/grid intrinsic sizing: Simple responsive adjustments
(wrapping, auto-fit grids).
The :has() Selector
Select elements based on their descendants or siblings. The "parent selector."
/* Style parent based on child */
.card:has(.featured) {
border: 2px solid var(--accent);
}
/* Style element based on sibling */
h1:has(+ h2) {
margin-bottom: 0.25rem;
}
/* Logical OR -- has either */
.form:has(:invalid) {
border-color: red;
}
/* Logical AND -- has both */
.card:has(img):has(.badge) {
/* card with both image AND badge */
}Performance Rules
- *Anchor to specific elements, not `body`, `:root`, or ``.**
Broad anchors force expensive re-evaluation on every DOM change.
- Use direct child (`>`) or sibling (`+`, `~`) combinators inside
:has() to limit traversal scope.
- Cannot nest `:has()` inside `:has()`.
- Pseudo-elements are not valid inside
:has().
/* Bad -- broad anchor, full subtree traversal */
body:has(.sidebar-open) { /* ... */ }
/* Good -- specific anchor, direct child */
.layout:has(> .sidebar-open) { /* ... */ }Custom Properties (CSS Variables)
:root {
--color-primary: #0066cc;
--spacing-md: 1rem;
}
.button {
background: var(--color-primary);
padding: var(--spacing-md);
}Rules
- Define on `:root` for globals. Scope to components for local overrides.
- Always provide fallbacks for component-level variables:
var(--button-bg, var(--color-primary)).
- Case-sensitive.
--my-colordiffers from--My-Color. - Custom properties inherit by default (unlike most CSS properties).
- Use `@property` for typed variables:
@property --gradient-angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}@property enables:
- Type checking (invalid values fall back to
initial-value) - Controlled inheritance (
inherits: false) - Animatable custom properties (critical for transitions)
Naming Conventions
:root {
/* Design tokens -- semantic */
--color-text-primary: #1a1a1a;
--color-bg-surface: #ffffff;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
/* Component tokens -- scoped */
--button-bg: var(--color-primary);
--button-radius: 4px;
}Use kebab-case. Prefix with category: --color-, --spacing-, --font-.
View Transitions
Animate between DOM states or page navigations.
Same-Document (SPA)
document.startViewTransition(() => {
// Update DOM here
});/* Default crossfade */
::view-transition-old(root) {
animation: fade-out 0.25s ease;
}
::view-transition-new(root) {
animation: fade-in 0.25s ease;
}Named Transitions
.card {
view-transition-name: card-hero;
}
/* Target specific element transition */
::view-transition-group(card-hero) {
animation-duration: 0.3s;
}Cross-Document (MPA)
@view-transition {
navigation: auto;
}Rules
- `view-transition-name` must be unique per page at transition time.
- Keep transitions short -- 200-400ms for UI, longer for page-level.
- Respect `prefers-reduced-motion`:
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation-duration: 0.01ms !important;
}
}Responsive Design
Modern responsive CSS: fluid sizing, container queries, media queries, and logical properties.
Responsive Hierarchy
Use the right tool for each level of responsiveness:
| Level | Tool | When |
|---|---|---|
| Content-driven | Flexbox wrapping, min()/max()/clamp() | Always -- baseline |
| Container-driven | Container queries, cqi/cqw units | Component adapts to parent size |
| Viewport-driven | Media queries, vw/vh/dvh units | Page-level layout changes |
| User preference | prefers-* media queries | Color scheme, motion, contrast |
Design from the inside out: Start with intrinsic sizing, add container queries for component-level adaptation, use media queries only for viewport-dependent elements (navigation, full-width sections).
Fluid Sizing with clamp()
Replace fixed breakpoints with fluid ranges:
/* Font size: 1rem minimum, fluid middle, 3rem maximum */
h1 {
font-size: clamp(1.5rem, 1rem + 2.5vw, 3rem);
}
/* Spacing: fluid padding */
.section {
padding: clamp(1rem, 5vw, 4rem) clamp(1rem, 3vw, 2rem);
}
/* Container width */
.container {
width: min(100% - 2rem, 1200px);
margin-inline: auto;
}Fluid Type Scale
:root {
--step-0: clamp(1rem, 0.5rem + 1.5vw, 1.25rem);
--step-1: clamp(1.25rem, 0.75rem + 2vw, 1.75rem);
--step-2: clamp(1.5rem, 1rem + 2.5vw, 2.5rem);
--step-3: clamp(2rem, 1.25rem + 3vw, 3.5rem);
}Container-Relative Fluid Sizing
Replace vw with cqi for container-scoped fluid values:
.card-wrapper {
container-type: inline-size;
}
.card__title {
font-size: clamp(1rem, 2.5cqi + 0.5rem, 2rem);
}
.card__content > * + * {
margin-top: clamp(0.5rem, 1cqi + 0.5rem, 1.5rem);
}Media Queries
Modern Syntax
Use range syntax (widely supported):
/* Old */
@media (min-width: 768px) and (max-width: 1023px) { }
/* Modern -- preferred */
@media (768px <= width < 1024px) { }
@media (width >= 768px) { }Breakpoint Strategy
Don't use fixed device breakpoints. Let content determine breakpoints.
/* Bad -- device-specific */
@media (min-width: 768px) { } /* "tablet" */
@media (min-width: 1024px) { } /* "desktop" */
/* Good -- content-driven */
@media (width >= 45rem) { } /* when content needs more space */Use `rem` for breakpoints -- respects user font size preferences.
Single Container Max-Width
/* One max-width, no fixed-width breakpoint ladder */
.container {
width: min(100% - 2rem, 75rem);
margin-inline: auto;
}Avoid the pattern of multiple max-width values at different breakpoints. It wastes space on intermediate viewport sizes.
Height Queries
/* Sticky header only when enough vertical space */
@media (height >= 40rem) {
.site-header {
position: sticky;
top: 0;
}
}Interaction Queries
/* Hover effects only on devices with hover capability */
@media (hover: hover) {
.card:hover { transform: translateY(-2px); }
}
/* Fine pointer (mouse) vs coarse (touch) */
@media (pointer: coarse) {
.button { min-height: 44px; min-width: 44px; }
}User Preference Queries
Color Scheme
:root {
color-scheme: light dark;
--color-text: #1a1a1a;
--color-bg: #ffffff;
}
@media (prefers-color-scheme: dark) {
:root {
--color-text: #e0e0e0;
--color-bg: #121212;
}
}Reduced Motion
/* Default: with motion */
.element {
transition: transform 0.3s ease;
}
@media (prefers-reduced-motion: reduce) {
.element {
transition: none;
}
}
/* Or: progressive enhancement approach */
.element {
transition: none; /* default: no motion */
}
@media (prefers-reduced-motion: no-preference) {
.element {
transition: transform 0.3s ease;
}
}High Contrast
@media (prefers-contrast: more) {
:root {
--border-color: #000;
--text-color: #000;
}
}Logical Properties
Write CSS that adapts to writing direction (LTR/RTL) automatically.
/* Physical (avoid for layout properties) */
margin-left: 1rem;
padding-right: 2rem;
border-top: 1px solid;
text-align: left;
/* Logical (preferred) */
margin-inline-start: 1rem;
padding-inline-end: 2rem;
border-block-start: 1px solid;
text-align: start;Mapping
| Physical | Logical (horizontal writing mode) |
|---|---|
left/right | inline-start/inline-end |
top/bottom | block-start/block-end |
width | inline-size |
height | block-size |
margin-left | margin-inline-start |
padding-top | padding-block-start |
border-right | border-inline-end |
text-align: left | text-align: start |
Shorthand
/* Block (top/bottom) and inline (left/right) */
margin-block: 1rem 2rem; /* block-start block-end */
margin-inline: auto; /* both inline directions */
padding-block: 1rem; /* same for both */Use logical properties for:
- Margins, padding, borders (layout-sensitive)
- Text alignment
- Positioning offsets (
inset-inline-startinstead ofleft)
Physical properties are fine for:
- Visual effects not affected by writing direction (box-shadow offsets)
- Explicit design decisions that shouldn't flip
Responsive Images
img {
max-width: 100%;
height: auto;
display: block;
}
/* Aspect ratio preservation */
.hero-image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}Anti-Patterns
| Don't | Do |
|---|---|
Fixed device breakpoints (768px, 1024px) | Content-driven breakpoints in rem |
px for breakpoints | rem (respects user font size) |
Multiple max-width ladder | width: min(100% - 2rem, 75rem) |
vw units without clamp | clamp(min, preferred, max) |
Font size in vw only (blows up on large screens) | clamp() with rem + vw |
Hiding content with display: none at breakpoints | Restructure layout with flexbox/grid |
| Physical properties for layout | Logical properties |
@media (hover: hover) without fallback | Progressive enhancement |
SCSS / Dart Sass
Modern Dart Sass with the @use/@forward module system.
Module System
`@import` is deprecated as of Dart Sass 1.80.0 and will be removed in Dart Sass 3.0.0. Use @use and @forward exclusively.
@use
Loads modules with namespaced access. Each module loaded once regardless of how many files @use it.
// Load with default namespace (filename)
@use 'variables';
.button {
color: variables.$primary;
@include variables.rounded;
}
// Custom namespace
@use 'variables' as vars;
.button { color: vars.$primary; }
// No namespace (use sparingly, only for your own files)
@use 'variables' as *;
.button { color: $primary; }Rules:
@usemust appear before any rules except@forward.- Namespace defaults to the last component of the URL (without extension).
- Members are scoped to the loading file -- not globally available.
- Each module loaded exactly once -- no duplicate CSS output.
@forward
Re-exports a module's members for downstream consumers. Used to create public API entrypoints.
// _index.scss -- library entrypoint
@forward 'colors';
@forward 'typography';
@forward 'spacing';Adding prefixes:
// Prefix all forwarded members
@forward 'buttons' as btn-*;
// Consumers: button.$btn-primary, @include button.btn-roundedControlling visibility:
@forward 'internal' hide $private-var, secret-mixin;
@forward 'internal' show $public-var, public-mixin;Configuration passthrough:
// _opinionated.scss
@forward 'library' with (
$primary: #0066cc !default,
$border-radius: 4px !default
);Module Configuration
// _theme.scss
$primary: #0066cc !default;
$font-stack: system-ui, sans-serif !default;
// main.scss -- configure on first load
@use 'theme' with (
$primary: #ff6600,
$font-stack: 'Inter', sans-serif
);Configuration applies globally for that module -- all subsequent @use of the same module see the configured values.
Private Members
Prefix with - or _ to make members private to the module:
// _helpers.scss
$-internal-spacing: 8px; // private
$public-spacing: 16px; // public
@mixin -internal-reset { /* private */ }
@mixin public-reset { /* public */ }File Organization
styles/
├── _index.scss # @forward entrypoint
├── abstracts/
│ ├── _index.scss # @forward variables, mixins, functions
│ ├── _variables.scss
│ ├── _mixins.scss
│ └── _functions.scss
├── base/
│ ├── _index.scss
│ ├── _reset.scss
│ └── _typography.scss
├── components/
│ ├── _index.scss
│ ├── _button.scss
│ └── _card.scss
├── layout/
│ ├── _index.scss
│ ├── _header.scss
│ └── _grid.scss
└── main.scss # @use 'abstracts', 'base', etc.Index files: _index.scss in a folder loads automatically when you @use the folder name: @use 'abstracts' loads abstracts/_index.scss.
Partials: Files prefixed with _ are partials -- not compiled standalone. Omit the _ in @use paths.
Built-in Modules
Access via @use "sass:module":
@use "sass:math";
@use "sass:color";
@use "sass:string";
@use "sass:list";
@use "sass:map";
@use "sass:meta";
@use "sass:selector";@use "sass:math";
@use "sass:color";
.element {
width: math.div(100%, 3); // Not 100% / 3
color: color.adjust($primary, $lightness: -10%);
}The `/` operator for division is deprecated. Use math.div().
SCSS Features
Variables
$primary: #0066cc;
$spacing: (
sm: 0.5rem,
md: 1rem,
lg: 2rem,
);Mixins
@mixin respond-to($breakpoint) {
@if $breakpoint == 'md' {
@media (width >= 768px) { @content; }
} @else if $breakpoint == 'lg' {
@media (width >= 1024px) { @content; }
}
}
.card {
padding: 1rem;
@include respond-to('md') {
padding: 2rem;
}
}Functions
@use "sass:math";
@function rem($px, $base: 16) {
@return math.div($px, $base) * 1rem;
}
.element {
font-size: rem(18); // 1.125rem
}Placeholder Selectors and @extend
%visually-hidden {
position: absolute;
width: 1px;
height: 1px;
clip: rect(0 0 0 0);
overflow: hidden;
}
.sr-only {
@extend %visually-hidden;
}Prefer mixins over `@extend` in most cases. @extend produces unexpected selectors and doesn't work across media queries.
Maps for Design Tokens
@use "sass:map";
$colors: (
'primary': #0066cc,
'secondary': #6c757d,
'danger': #dc3545,
);
@function color($name) {
@return map.get($colors, $name);
}
.alert {
background: color('danger');
}Loops for Utility Generation
@use "sass:map";
$spacing: (0: 0, 1: 0.25rem, 2: 0.5rem, 3: 1rem, 4: 2rem);
@each $key, $value in $spacing {
.mt-#{$key} { margin-top: $value; }
.mb-#{$key} { margin-bottom: $value; }
.p-#{$key} { padding: $value; }
}Migration from @import
Use the official migrator:
npm install -g sass-migrator
sass-migrator module --migrate-deps your-entrypoint.scssFor built-in functions only (leave @import for now):
sass-migrator module --built-in-only your-entrypoint.scssKey Changes
@import | @use/@forward |
|---|---|
| Global namespace | Namespaced access |
| Loads multiple times | Loads once |
| Variables globally available | Scoped to loading file |
@import "file" | @use "file" |
| No visibility control | hide/show in @forward |
$var: value !global | @use ... with ($var: value) |
lighten($color, 10%) | color.adjust($color, $lightness: 10%) |
percentage(0.5) | math.percentage(0.5) |
Anti-Patterns
| Don't | Do |
|---|---|
@import | @use and @forward |
Global variables without !default | $var: value !default for configurable modules |
@extend across components | @mixin -- more predictable output |
| Deep nesting (> 3 levels) | Flatten selectors |
100% / 3 division | math.div(100%, 3) |
lighten() / darken() globals | color.adjust() from sass:color |
Barrel files with @use of everything | @forward in _index.scss entrypoints |