v130 · css
Migration Guide
Step-by-step migration from SASS/SCSS nesting to native CSS nesting with Chrome 130's stable bare declarations. Each step shows the before/after diff and how CSSNestedDeclarations preserves source order.
.card { background: white; border: 1px solid #ccc; padding: 1rem; &__title { font-size: 1.25rem; font-weight: 700; } &__body { color: #555; line-height: 1.6; } }
.card { background: white; border: 1px solid #ccc; padding: 1rem; & .card__title { font-size: 1.25rem; font-weight: 700; } & .card__body { color: #555; line-height: 1.6; } }
✓ No behavioural change
Bare declarations before nested rules work identically in SASS and native CSS.
ℹ BEM: use descendant selector
SASS's &__title compiles to .card__title. Native CSS nesting uses a descendant: & .card__title.
&:hover, &:focus, &:disabled — these are identical between SASS and native CSS. Direct copy-paste works.
.btn { background: #2855ad; color: white; &:hover { background: #1a3a80; } &:focus-visible { outline: 2px solid #2855ad; outline-offset: 2px; } &:disabled { opacity: 0.45; cursor: not-allowed; } }
.btn { background: #2855ad; color: white; &:hover { background: #1a3a80; } &:focus-visible { outline: 2px solid #2855ad; outline-offset: 2px; } &:disabled { opacity: 0.45; cursor: not-allowed; } }
✓ Zero changes needed
Pseudo-class nesting syntax is identical. Paste as-is.
ℹ Specificity unchanged
Native nesting computes the same specificity as the SASS-compiled output.
CSSNestedDeclarations, restoring correct behaviour.
.chip { background: #eee; color: #222; &.active { background: #2855ad; color: white; } /* OLD: this was hoisted BEFORE &.active → */ /* .active override could beat it. BUG. */ background: #ddd; ← hoisted, wrong order }
.chip { background: #eee; color: #222; &.active { background: #2855ad; color: white; } /* Chrome 130: wrapped in CSSNestedDeclarations */ /* stays in source order — wins over &.active */ background: #ddd; ← correct position ✓ }
⚠ Breaking change in pre-130
If your SASS relied on late bare declarations overriding nested rules, pre-130 browsers may render incorrectly.
✓ Chrome 130 matches SASS
CSSNestedDeclarations wraps late bare decls so source order is preserved — matching compiled SASS output.
@layer for grouping or @property for typed tokens. There is no direct mixin equivalent, but the cascade gives you similar control.
// SASS mixin definition @mixin focus-ring { outline: 2px solid currentColor; outline-offset: 2px; } .btn { background: blue; color: white; &:focus-visible { // include mixin @include focus-ring; } }
/* Shared styles in a base layer */ @layer base { :focus-visible { outline: 2px solid currentColor; outline-offset: 2px; } } .btn { background: blue; color: white; /* :focus-visible inherits from @layer base */ &:focus-visible { /* override only if needed */ } }
ℹ No 1:1 mixin equivalent
Native CSS has no mixin system. Use @layer for shared base styles, or custom properties for dynamic values.
✓ Nesting works inside @layer
All CSS nesting syntax is valid inside @layer blocks.
@media queries directly inside a rule, exactly like SASS. This works in Chrome 130 and all modern browsers.
.grid { display: grid; grid-template-columns: 1fr; gap: 1rem; @media (min-width: 640px) { grid-template-columns: 1fr 1fr; } @media (min-width: 1024px) { grid-template-columns: repeat(3, 1fr); } }
.grid { display: grid; grid-template-columns: 1fr; gap: 1rem; @media (min-width: 640px) { grid-template-columns: 1fr 1fr; } @media (min-width: 1024px) { grid-template-columns: repeat(3, 1fr); } }
✓ Zero changes needed
@media inside a rule works identically in native CSS. The implied & is applied automatically.
ℹ @container works too
@container queries inside rules follow the same pattern.