Notes for CSS features.
What it is / problem solved: ARIA attributes (aria-*) mark up accessibility state in HTML. CSS
can select on these attributes directly, so the same attribute that tells screen readers "this is
expanded" also drives the visual style — no need for a parallel .is-open class that can drift out
of sync with real state.
Docs: https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors and https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA
How it works:
HTML: <button aria-expanded="false">Menu</button>
CSS: button[aria-expanded="true"] { ... }
^ single source of truth for both
accessibility tree AND visual style
Examples:
/* Style an accordion trigger when expanded */
[aria-expanded="true"] { background: #eef; }
/* Visually + semantically hide an element */
[aria-hidden="true"] { display: none; }
/* Flag invalid form fields */
[aria-invalid="true"] { border-color: red; }
/* Highlight current page in nav */
[aria-current="page"] { font-weight: bold; }
/* Style a disabled-looking but focusable control */
[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }What it is / problem solved: A one-dimensional layout model for arranging items along a single axis (row or column) and distributing extra space between them. Solves the "align/space these items in a line" problem that used to require floats/hacks.
Docs: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout
How it works:
main axis (justify-content) →
┌──────────────────────────────┐
│ [item1] [item2] [item3] │ ↕ cross axis
└──────────────────────────────┘ (align-items)
display: flex on the container; flex-* props on children
Examples:
.container {
display: flex;
justify-content: space-between; /* spacing along main axis */
align-items: center; /* alignment along cross axis */
gap: 1rem; /* spacing between items */
}
.item {
flex: 1 1 200px; /* grow shrink basis — item can grow/shrink from 200px */
}
.nav {
display: flex;
flex-direction: column; /* stack items vertically instead of in a row */
}
.card-footer {
display: flex;
margin-left: auto; /* push item to the far end of the flex line */
}What it is / problem solved: A two-dimensional layout system — rows and columns at once. Solves page/component layouts (headers, sidebars, content areas) that flexbox can only approximate with nesting.
Docs: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout
How it works:
grid-template-columns: 1fr 2fr 1fr
grid-template-rows: auto 1fr auto
+--------+------------+--------+
| header (spans all 3 columns) |
+--------+------------+--------+
| side | content | aside |
+--------+------------+--------+
| footer (spans all 3 columns) |
+--------+------------+--------+
Examples:
.container {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 3 equal columns */
grid-template-rows: auto 1fr auto;
gap: 1rem;
}
.sidebar {
grid-column: 1 / 2; /* place explicitly in column 1 */
}
/* Responsive grid without media queries: as many 200px+ columns as fit */
.responsive-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.header {
grid-area: header; /* place via named area (see grid-template-areas) */
}
.layout {
grid-template-areas:
"header header"
"sidebar content"
"footer footer";
}What it is / problem solved: @keyframes + animation (and the related transition property)
animate CSS property changes over time, without JS. Solves state-change feedback (hover, load,
loading spinners) cheaply and performantly (especially transform/opacity, which are
GPU-accelerated).
Docs: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations and https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_transitions
How it works:
transition: interpolates between two states (A → B) on a trigger (e.g. :hover)
@keyframes: defines a timeline of states, animation plays it (once or looped)
0% ─────────────────── 50% ─────────────────── 100%
state A state C state B
Examples:
/* Transition: simplest case, animate on hover */
.button {
transition: background-color 0.2s ease;
}
.button:hover { background-color: #333; }
/* Keyframe animation: continuous spin */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spinner { animation: spin 1s linear infinite; }
/* Keyframes with multiple stages */
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
.badge { animation: pulse 2s ease-in-out infinite; }
/* Entrance animation, plays once */
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.toast { animation: fade-in-up 0.3s ease-out; }What it is / problem solved: A set of native CSS features (not one single feature) that let styles behave the way component-based JS frameworks expect: scoped to a component, themeable via variables, and responsive to the component's own size rather than the viewport. Solves style leakage/collision and "global CSS doesn't know about my component boundaries."
Docs:
- Custom properties: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascading_variables
- Container queries: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries
:is()/:where(): https://developer.mozilla.org/en-US/docs/Web/CSS/:is- Native nesting: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting
- Shadow DOM (hard scoping): https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM
How it works:
┌─ .card (container) ───────────────────┐
│ --card-padding: 1rem; ← theming │
│ │
│ @container (min-width: 400px) {...} │ ← responds to *its own*
│ │ size, not the viewport
│ &:hover { ... } ← nesting │
└───────────────────────────────────────┘
Examples:
/* Custom properties: design tokens scoped to a component, overridable per-instance */
.card {
--card-padding: 1rem;
padding: var(--card-padding);
}
.card--compact { --card-padding: 0.5rem; }
/* Container queries: layout reacts to the component's own width */
.card-wrapper { container-type: inline-size; }
@container (min-width: 400px) {
.card { flex-direction: row; }
}
/* :where() keeps specificity at 0, so consumers can override easily */
:where(.card, .panel) h2 { margin-top: 0; }
/* Native nesting: keep component styles visually grouped */
.card {
border-radius: 8px;
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
& .title {
font-weight: 600;
}
}