Advanced Nesting and Selector Techniques
1. Deep Nesting and BEM Integration
| Pattern | Syntax | Output | Best Practice |
|---|---|---|---|
| BEM Block | .block { } |
.block | Top-level component |
| BEM Element | &__element { } |
.block__element | Part of block |
| BEM Modifier | &--modifier { } |
.block--modifier | Variation of block |
| Combined BEM | &__elem--mod { } |
.block__elem--mod | Modified element |
| Parent reference | & { } |
Full parent selector | Modifier contexts |
| Max nesting depth | 3-4 levels max | Avoid specificity wars | Flatten if deeper |
Example: BEM methodology with SCSS
// Perfect BEM with SCSS nesting
.card {
padding: 1rem;
background: white;
border-radius: 8px;
// BEM Element: .card__header
&__header {
padding: 1rem;
border-bottom: 1px solid #ddd;
// Nested element: .card__header__title
&__title {
font-size: 1.5rem;
font-weight: bold;
margin: 0;
}
&__subtitle {
font-size: 0.875rem;
color: #666;
}
}
// BEM Element: .card__body
&__body {
padding: 1rem;
}
// BEM Element: .card__footer
&__footer {
padding: 1rem;
border-top: 1px solid #ddd;
text-align: right;
}
// BEM Modifier: .card--featured
&--featured {
border: 2px solid gold;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
// Modified element: .card--featured .card__header
.card__header {
background: gold;
}
}
// BEM Modifier: .card--compact
&--compact {
padding: 0.5rem;
.card__header,
.card__body,
.card__footer {
padding: 0.5rem;
}
}
// State modifier: .card.is-loading
&.is-loading {
opacity: 0.5;
pointer-events: none;
}
}
// Alternative: Element modifiers
.button {
display: inline-block;
padding: 10px 20px;
// Element: .button__icon
&__icon {
margin-right: 8px;
// Element modifier: .button__icon--left
&--left {
margin-right: 8px;
margin-left: 0;
}
// Element modifier: .button__icon--right
&--right {
margin-left: 8px;
margin-right: 0;
}
}
// Modifier: .button--primary
&--primary {
background: blue;
color: white;
}
// Modifier: .button--large
&--large {
padding: 15px 30px;
font-size: 1.2rem;
}
}
// Advanced: BEM with state classes
.nav {
display: flex;
&__item {
padding: 10px 20px;
// Combined: .nav__item--active
&--active {
background: blue;
color: white;
}
// State: .nav__item.is-disabled
&.is-disabled {
opacity: 0.5;
pointer-events: none;
}
// Pseudo-class: .nav__item:hover
&:hover:not(.is-disabled) {
background: lightblue;
}
}
}
// Practical: BEM mixin generator
@mixin element($name) {
&__#{$name} {
@content;
}
}
@mixin modifier($name) {
&--#{$name} {
@content;
}
}
.menu {
display: flex;
@include element(item) {
padding: 10px;
@include modifier(active) {
background: blue;
color: white;
}
}
}
// Output:
// .menu { display: flex; }
// .menu__item { padding: 10px; }
// .menu__item--active { background: blue; color: white; }
// Nesting depth warning (BAD - too deep)
.header {
.container {
.nav {
.menu {
.item {
.link {
// 6 levels - TOO DEEP!
color: blue;
}
}
}
}
}
}
// Better: Flatten with BEM
.header { }
.header__nav { }
.header__menu { }
.header__menu-item { }
.header__menu-link { color: blue; }
2. Selector Functions for Dynamic Generation
| Function | Use Case | Example | Output |
|---|---|---|---|
| selector.nest() | Programmatic nesting | nest('.a', '.b') |
.a .b |
| selector.append() | Compound selectors | append('.a', ':hover') |
.a:hover |
| selector.replace() | Selector transformation | replace('.old', '.new') |
Replace part |
| selector.unify() | Combine selectors | unify('div', '.class') |
div.class |
| & in string | Parent interpolation | #{&}--modifier |
Dynamic BEM |
Example: Dynamic selector generation
@use 'sass:selector';
@use 'sass:list';
// Dynamic BEM modifier generator
@mixin bem-modifiers($modifiers...) {
@each $modifier in $modifiers {
&--#{$modifier} {
@content ($modifier);
}
}
}
.button {
padding: 10px 20px;
@include bem-modifiers(primary, secondary, danger) using ($mod) {
@if $mod == primary {
background: blue;
} @else if $mod == secondary {
background: gray;
} @else if $mod == danger {
background: red;
}
}
}
// Selector nesting with functions
.dynamic-nest {
$parent: '.container';
$child: '.item';
// Programmatic nesting
#{selector.nest($parent, $child)} {
display: block;
}
// Outputs: .container .item { display: block; }
}
// Append pseudo-classes
@mixin hover-focus {
$selector: &;
#{selector.append($selector, ':hover')},
#{selector.append($selector, ':focus')} {
@content;
}
}
.link {
color: blue;
@include hover-focus {
color: darkblue;
text-decoration: underline;
}
}
// Generate utility classes dynamically
$sides: (top, right, bottom, left);
$sizes: (sm: 8px, md: 16px, lg: 24px, xl: 32px);
@each $side in $sides {
@each $size-name, $size-value in $sizes {
.m-#{$side}-#{$size-name} {
margin-#{$side}: $size-value;
}
.p-#{$side}-#{$size-name} {
padding-#{$side}: $size-value;
}
}
}
// Advanced: Context-aware selectors
@mixin theme-context($themes...) {
@each $theme in $themes {
.theme-#{$theme} & {
@content ($theme);
}
}
}
.card {
background: white;
@include theme-context(dark, high-contrast) using ($theme) {
@if $theme == dark {
background: #333;
color: white;
} @else if $theme == high-contrast {
background: black;
color: yellow;
border: 2px solid yellow;
}
}
}
// Outputs:
// .theme-dark .card { background: #333; color: white; }
// .theme-high-contrast .card { background: black; color: yellow; ... }
// Practical: Responsive state variants
@mixin variants($variants...) {
@each $variant in $variants {
&-#{$variant} {
@content ($variant);
}
}
}
.text {
@include variants(center, left, right) using ($align) {
text-align: $align;
}
}
// Selector replacement
$old-prefix: 'btn';
$new-prefix: 'button';
@function migrate-selector($selector) {
@return selector.replace($selector, '.#{$old-prefix}', '.#{$new-prefix}');
}
// Practical: Attribute selector generator
@mixin data-state($states...) {
@each $state in $states {
&[data-state="#{$state}"] {
@content ($state);
}
}
}
.component {
@include data-state(loading, error, success) using ($state) {
@if $state == loading {
opacity: 0.5;
} @else if $state == error {
border-color: red;
} @else if $state == success {
border-color: green;
}
}
}
3. Pseudo-selector Nesting Patterns
| Pattern | Syntax | Output | Use Case |
|---|---|---|---|
| :hover | &:hover { } |
.element:hover | Mouse over state |
| :focus | &:focus { } |
.element:focus | Keyboard focus |
| :active | &:active { } |
.element:active | Click/press state |
| ::before/::after | &::before { } |
.element::before | Pseudo-elements |
| :not() | &:not(.class) { } |
.element:not(.class) | Exclusion selector |
| :nth-child() | &:nth-child(n) { } |
.element:nth-child(n) | Positional selection |
| Combined | &:hover:not(:disabled) |
Multiple pseudos | Complex states |
Example: Pseudo-selector patterns
// Interactive states
.button {
background: blue;
color: white;
transition: all 0.3s;
// Hover state
&:hover {
background: darkblue;
transform: translateY(-2px);
}
// Focus state (accessibility)
&:focus {
outline: 2px solid orange;
outline-offset: 2px;
}
// Active (pressed) state
&:active {
transform: translateY(0);
box-shadow: inset 0 2px 4px rgba(0,0,0,0.3);
}
// Disabled state
&:disabled {
opacity: 0.5;
cursor: not-allowed;
// No hover when disabled
&:hover {
background: blue;
transform: none;
}
}
// Combined: hover but not disabled
&:hover:not(:disabled) {
cursor: pointer;
}
// Focus-visible (modern accessibility)
&:focus-visible {
outline: 2px solid orange;
}
&:focus:not(:focus-visible) {
outline: none;
}
}
// Pseudo-elements
.card {
position: relative;
// Before pseudo-element
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, blue, purple);
}
// After pseudo-element
&::after {
content: '→';
margin-left: 8px;
transition: margin-left 0.3s;
}
&:hover::after {
margin-left: 12px;
}
}
// Structural pseudo-classes
.list {
&__item {
padding: 10px;
border-bottom: 1px solid #ddd;
// First child
&:first-child {
border-top: 1px solid #ddd;
}
// Last child
&:last-child {
border-bottom: none;
}
// Nth child patterns
&:nth-child(odd) {
background: #f9f9f9;
}
&:nth-child(even) {
background: white;
}
// Every 3rd item
&:nth-child(3n) {
font-weight: bold;
}
// First 3 items
&:nth-child(-n + 3) {
border-left: 3px solid blue;
}
// Last 2 items
&:nth-last-child(-n + 2) {
opacity: 0.7;
}
}
}
// Form pseudo-classes
.input {
padding: 10px;
border: 1px solid #ccc;
&:focus {
border-color: blue;
box-shadow: 0 0 0 3px rgba(0,0,255,0.1);
}
&:valid {
border-color: green;
}
&:invalid {
border-color: red;
}
&:required {
border-left: 3px solid orange;
}
&:disabled {
background: #f5f5f5;
cursor: not-allowed;
}
&:read-only {
background: #f9f9f9;
}
&:placeholder-shown {
border-style: dashed;
}
&::placeholder {
color: #999;
font-style: italic;
}
}
// Link states (LVHA order)
.link {
color: blue;
text-decoration: none;
// L - Link (unvisited)
&:link {
color: blue;
}
// V - Visited
&:visited {
color: purple;
}
// H - Hover
&:hover {
color: darkblue;
text-decoration: underline;
}
// A - Active
&:active {
color: red;
}
}
// Practical: Hover/focus mixin
@mixin interactive-state {
&:hover,
&:focus {
@content;
}
}
.card {
@include interactive-state {
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
transform: translateY(-2px);
}
}
// Practical: Not disabled mixin
@mixin when-enabled {
&:not(:disabled):not(.is-disabled) {
@content;
}
}
.button {
@include when-enabled {
cursor: pointer;
&:hover {
background: darkblue;
}
}
}
// CSS :has() and :is() (modern)
.card {
// Has child selector
&:has(> .card__image) {
padding-top: 0;
}
// Is selector (shorthand)
&:is(:hover, :focus) {
outline: 2px solid blue;
}
// Where (zero specificity)
&:where(:hover, :focus) {
opacity: 0.9;
}
}
4. Media Query Nesting and Responsive Mixins
| Pattern | Syntax | Description | Best Practice |
|---|---|---|---|
| Nested @media | @media (min-width) { } |
Inside selector | Keeps context together |
| Breakpoint mixin | @include bp(md) { } |
Named breakpoints | Consistent breakpoints |
| Mobile-first | min-width |
Base → larger | Preferred approach |
| Desktop-first | max-width |
Desktop → smaller | Legacy approach |
| Range queries | min and max |
Between breakpoints | Specific targeting |
Example: Responsive design with nested media queries
@use 'sass:map';
// Breakpoint configuration
$breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px,
xxl: 1400px
);
// Mobile-first mixin (min-width)
@mixin above($breakpoint) {
$value: map.get($breakpoints, $breakpoint);
@if $value {
@media (min-width: $value) {
@content;
}
} @else {
@media (min-width: $breakpoint) {
@content;
}
}
}
// Desktop-first mixin (max-width)
@mixin below($breakpoint) {
$value: map.get($breakpoints, $breakpoint);
@if $value {
@media (max-width: $value - 1px) {
@content;
}
} @else {
@media (max-width: $breakpoint) {
@content;
}
}
}
// Between two breakpoints
@mixin between($min, $max) {
$min-val: map.get($breakpoints, $min) or $min;
$max-val: map.get($breakpoints, $max) or $max;
@media (min-width: $min-val) and (max-width: $max-val - 1px) {
@content;
}
}
// Nested media queries example
.container {
width: 100%;
padding: 1rem;
// Mobile-first responsive design
@include above(sm) {
max-width: 540px;
margin: 0 auto;
}
@include above(md) {
max-width: 720px;
padding: 1.5rem;
}
@include above(lg) {
max-width: 960px;
padding: 2rem;
}
@include above(xl) {
max-width: 1140px;
}
@include above(xxl) {
max-width: 1320px;
}
}
// Responsive typography
.heading {
font-size: 1.5rem;
line-height: 1.2;
@include above(md) {
font-size: 2rem;
}
@include above(lg) {
font-size: 2.5rem;
}
@include above(xl) {
font-size: 3rem;
line-height: 1.1;
}
}
// Responsive grid
.grid {
display: grid;
gap: 1rem;
// Mobile: 1 column
grid-template-columns: 1fr;
// Tablet: 2 columns
@include above(md) {
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
}
// Desktop: 3 columns
@include above(lg) {
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
}
// Large desktop: 4 columns
@include above(xl) {
grid-template-columns: repeat(4, 1fr);
}
}
// Only for specific range
.sidebar {
display: none;
// Only show between md and lg
@include between(md, lg) {
display: block;
width: 200px;
}
@include above(lg) {
display: block;
width: 250px;
}
}
// Media query nesting with elements
.card {
padding: 1rem;
&__image {
width: 100%;
height: 200px;
@include above(md) {
height: 300px;
}
}
&__title {
font-size: 1.25rem;
@include above(md) {
font-size: 1.5rem;
}
}
}
// Advanced: Orientation queries
@mixin landscape {
@media (orientation: landscape) {
@content;
}
}
@mixin portrait {
@media (orientation: portrait) {
@content;
}
}
.video-player {
aspect-ratio: 16/9;
@include portrait {
aspect-ratio: 4/3;
}
}
// Advanced: Custom media features
@mixin prefers-dark-mode {
@media (prefers-color-scheme: dark) {
@content;
}
}
@mixin prefers-reduced-motion {
@media (prefers-reduced-motion: reduce) {
@content;
}
}
.theme {
background: white;
color: black;
@include prefers-dark-mode {
background: #1a1a1a;
color: white;
}
}
.animated {
transition: all 0.3s;
@include prefers-reduced-motion {
transition: none;
}
}
// Container queries (modern)
@mixin container($size) {
@container (min-width: $size) {
@content;
}
}
.card {
container-type: inline-size;
&__title {
font-size: 1rem;
@include container(400px) {
font-size: 1.5rem;
}
@include container(600px) {
font-size: 2rem;
}
}
}
5. Keyframe Animation Nesting
| Pattern | Syntax | Description | Use Case |
|---|---|---|---|
| @keyframes | @keyframes name { } |
Define animation | Reusable animations |
| Dynamic names | @keyframes #{$name} |
Generated keyframes | Loop-based creation |
| Nested in selector | Inside component | Scoped animations | Component-specific |
| Percentage steps | 0%, 50%, 100% |
Animation timeline | Multi-step animations |
| from/to | from { } to { } |
Start and end | Simple animations |
Example: Keyframe animations in SCSS
// Basic keyframe definition
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fade-in {
animation: fadeIn 0.5s ease-in;
}
// Multi-step keyframes
@keyframes slideInBounce {
0% {
transform: translateX(-100%);
opacity: 0;
}
60% {
transform: translateX(10px);
opacity: 1;
}
80% {
transform: translateX(-5px);
}
100% {
transform: translateX(0);
}
}
// Dynamic keyframe generation
$animations: (
fadeIn: (from: (opacity: 0), to: (opacity: 1)),
slideDown: (from: (transform: translateY(-20px)), to: (transform: translateY(0))),
scaleUp: (from: (transform: scale(0.8)), to: (transform: scale(1)))
);
@each $name, $steps in $animations {
@keyframes #{$name} {
from {
@each $prop, $value in map.get($steps, from) {
#{$prop}: $value;
}
}
to {
@each $prop, $value in map.get($steps, to) {
#{$prop}: $value;
}
}
}
}
// Scoped animations (nested in component)
.loader {
// Keyframes defined within component scope
@keyframes loader-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
width: 50px;
height: 50px;
border: 3px solid #f3f3f3;
border-top: 3px solid blue;
border-radius: 50%;
animation: loader-spin 1s linear infinite;
}
// Complex multi-property animation
@keyframes pulse {
0% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(0, 123, 255, 0.7);
}
50% {
transform: scale(1.05);
box-shadow: 0 0 0 10px rgba(0, 123, 255, 0);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(0, 123, 255, 0);
}
}
// Animation mixin
@mixin animate($name, $duration: 1s, $timing: ease, $iteration: 1) {
animation-name: $name;
animation-duration: $duration;
animation-timing-function: $timing;
animation-iteration-count: $iteration;
}
.button {
@include animate(pulse, 2s, ease-out, infinite);
}
// Generate bounce animations
@for $i from 1 through 3 {
@keyframes bounce-#{$i} {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-#{$i * 10}px);
}
}
.bounce-#{$i} {
animation: bounce-#{$i} 0.6s ease-in-out infinite;
}
}
// Practical: Loading dots animation
@keyframes dot-pulse {
0%, 80%, 100% {
opacity: 0.3;
transform: scale(0.8);
}
40% {
opacity: 1;
transform: scale(1);
}
}
.loading-dots {
display: flex;
gap: 8px;
&__dot {
width: 10px;
height: 10px;
background: blue;
border-radius: 50%;
animation: dot-pulse 1.4s infinite ease-in-out;
&:nth-child(1) {
animation-delay: 0s;
}
&:nth-child(2) {
animation-delay: 0.2s;
}
&:nth-child(3) {
animation-delay: 0.4s;
}
}
}
// Practical: Slide animations from directions
$directions: (
left: translateX(-100%),
right: translateX(100%),
top: translateY(-100%),
bottom: translateY(100%)
);
@each $direction, $transform in $directions {
@keyframes slideIn-#{$direction} {
from {
transform: $transform;
opacity: 0;
}
to {
transform: translate(0, 0);
opacity: 1;
}
}
.slide-in-#{$direction} {
animation: slideIn-#{$direction} 0.5s ease-out;
}
}
6. At-rule Nesting and CSS Grid/Flexbox
| At-rule | Nesting Pattern | Description | Use Case |
|---|---|---|---|
| @media | Inside selectors | Responsive styles | Component breakpoints |
| @supports | Feature detection | Progressive enhancement | Modern CSS features |
| @container | Container queries | Size-based styling | Component-level responsive |
| @layer | Cascade layers | Style ordering | Specificity control |
| Grid nesting | display: grid children | Grid layouts | Complex layouts |
| Flex nesting | display: flex children | Flexbox layouts | 1D layouts |
Example: At-rule nesting and layout systems
// @supports nesting
.card {
display: block;
// Feature detection
@supports (display: grid) {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 1rem;
}
// Fallback for older browsers
@supports not (display: grid) {
display: flex;
flex-wrap: wrap;
}
}
// @supports with multiple features
.modern-layout {
display: block;
@supports (display: grid) and (gap: 1rem) {
display: grid;
gap: 1rem;
}
@supports (container-type: inline-size) {
container-type: inline-size;
}
}
// Nested grid system
.grid-container {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 1rem;
&__item {
// Default: full width (mobile)
grid-column: span 12;
// Tablet: half width
@media (min-width: 768px) {
grid-column: span 6;
}
// Desktop: third width
@media (min-width: 992px) {
grid-column: span 4;
}
// Modifiers
&--full {
grid-column: 1 / -1;
}
&--half {
@media (min-width: 768px) {
grid-column: span 6;
}
}
}
}
// Flexbox utilities with nesting
.flex {
display: flex;
// Direction modifiers
&--row {
flex-direction: row;
}
&--column {
flex-direction: column;
}
// Justify content
&--center {
justify-content: center;
}
&--between {
justify-content: space-between;
}
&--around {
justify-content: space-around;
}
// Align items
&--items-center {
align-items: center;
}
&--items-start {
align-items: flex-start;
}
&--items-end {
align-items: flex-end;
}
// Responsive direction
&--responsive {
flex-direction: column;
@media (min-width: 768px) {
flex-direction: row;
}
}
}
// Container queries (modern CSS)
.card-container {
container-type: inline-size;
container-name: card;
.card {
padding: 1rem;
// When container is at least 400px
@container card (min-width: 400px) {
display: grid;
grid-template-columns: 150px 1fr;
gap: 1rem;
padding: 1.5rem;
}
// When container is at least 600px
@container card (min-width: 600px) {
grid-template-columns: 200px 1fr 200px;
padding: 2rem;
}
}
}
// @layer for cascade control
@layer base, components, utilities;
@layer base {
.button {
padding: 10px 20px;
border: none;
}
}
@layer components {
.button {
&--primary {
background: blue;
color: white;
}
}
}
@layer utilities {
.button {
&.rounded {
border-radius: 9999px;
}
}
}
// Practical: Responsive grid mixin
@mixin grid-columns($mobile: 1, $tablet: 2, $desktop: 3, $wide: 4) {
display: grid;
gap: 1rem;
grid-template-columns: repeat($mobile, 1fr);
@media (min-width: 768px) {
grid-template-columns: repeat($tablet, 1fr);
gap: 1.5rem;
}
@media (min-width: 992px) {
grid-template-columns: repeat($desktop, 1fr);
gap: 2rem;
}
@media (min-width: 1200px) {
grid-template-columns: repeat($wide, 1fr);
}
}
.product-grid {
@include grid-columns(1, 2, 3, 4);
}
// Practical: Flex utilities generator
@each $justify in (start, center, end, between, around) {
.justify-#{$justify} {
display: flex;
justify-content: if($justify == between, space-between,
if($justify == around, space-around,
flex-#{$justify}));
}
}
@each $align in (start, center, end, stretch) {
.items-#{$align} {
display: flex;
align-items: if($align == start or $align == end,
flex-#{$align},
$align);
}
}
// Advanced: Subgrid support
.article-layout {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 2rem;
&__sidebar {
grid-column: 1 / 4;
}
&__content {
grid-column: 4 / -1;
// Subgrid for nested content
@supports (grid-template-columns: subgrid) {
display: grid;
grid-template-columns: subgrid;
gap: 1rem;
}
}
}
Advanced Nesting Summary
- BEM: Use
&__elementand&--modifierfor clean BEM syntax - Max depth: Limit nesting to 3-4 levels to avoid specificity issues
- Pseudo-selectors: Nest
:hover,:focus,::beforewith& - Media queries: Nest inside selectors for context-aware responsive design
- Keyframes: Can be nested in components or generated dynamically
- At-rules:
@supports,@container,@layerwork with nesting - Grid/Flex: Use nesting with modifiers for layout variations
Warning: Deep nesting increases specificity and makes styles
harder to override. Keep nesting shallow and use BEM for flat, maintainable selectors.