Table of contents
Open Table of contents
Modern CSS
Modern CSS refers to the various tooling and support that has grown to support CSS for ease of modern development.
- Container Queries : Components can own their own responsiveness.
<div class="container">
<div class="card">
<h2>Adaptive Title</h2>
<p>This text stays the same, but the title responds to the box size!</p>
</div>
</div>
/* 1. Define the container
container-type designates this as a container
*/
.container {
container-type: inline-size;
width: 100%; /* Or any width; the child reacts to this */
}
/* 2. Default mobile-first style */
.card h2 {
font-size: 1rem;
color: gray;
}
/* 3. Style based on the container's width (not the screen!)
Notice we can define a conditional based on the container width not the screen width; so we don't have to care about screen rules
*/
@container (width > 500px) {
.card h2 {
font-size: 2rem;
color: blue;
}
}
- :has() or parent selector : you can conditionally style based on what’s inside of the parent component
/* Default style for all articles */
article {
border: 1px solid #ccc;
padding: 1rem;
margin-bottom: 1rem;
}
/* Styles applied ONLY to an article that has an <img> descendant */
article:has(img) {
border: 2px solid blue;
background-color: #f0f8ff; /* Light blue background */
}
- Native Nesting CSS Support : allows you to nest styles
Before nesting, you would have to write it like so above. You can declare a conditional styling based on .card and then condition further style for a specific element tag as .card header. Note this will condition EVERY header even multiple children away.
/* Without nesting */
.card {
border: 1px solid lightgrey;
}
.card header {
border-bottom: 1px solid lightgrey;
background: #f1f3f4;
}
/* With native CSS nesting */
.card {
border: 1px solid lightgrey;
border-radius: 6px;
overflow: hidden;
header {
border-bottom: 1px solid lightgrey;
background: #f1f3f4;
padding: 16px;
}
}
/* Styling a link inside a paragraph, including hover effects */
p.example {
font-family: system-ui;
& > a {
/* Equivalent to p.example > a */
color: tomato;
&:hover, /* Equivalent to p.example > a:hover */
&:focus {
/* Equivalent to p.example > a:focus */
color: ivory;
background-color: tomato;
}
}
}
The above css declare styling for a paragraph tag (p) with class “example” AND > (child combinator) will ONLY apply to the direct child.
<p class="example">
<a href="#">This link WILL be styled</a>
</p>
This styles based a media query that the viewport is 960px or larger.
article {
padding: 1rem;
@media (width >= 960px) {
font-size: 2rem;
}
}
- Cascading Layers : Allows for using Layer Order over specificity Layers are addressed by origin of the stylesheet (browser, user, author). Unlayered style will always have the highest precedence. Then highest specificity otherwise, it’s last one wins (physically in the stylesheet).
/* 1. Declare the order of layers (lowest to highest precedence) */
@layer base, theme;
/* 2. Styles in the 'base' layer (lower precedence) */
@layer base {
.text {
color: grey; /* This will be overridden */
}
p {
font-size: 1rem;
}
}
/* 3. Styles in the 'theme' layer (higher precedence) */
@layer theme {
p {
color: aqua; /* This color wins, even with a less specific selector */
}
.text {
font-style: italic;
}
}
- Modern Logical Properties : Languages are not always left to right. Some are up to down. In that sense, a layer of indirection so that we say start to end aids us to style a component.
.box {
margin-inline-start: 20px;
padding-block-start: 10px;
border-inline-end: 2px solid red;
inline-size: 100px; /* Use inline-size instead of width */
}
Additional Tricks
-
Subgrid : Allows a nested grid item to borrow the grid of the parent If you need to align content inside different cards so share the same grid pattern.
-
Dynamic Viewport Units : dvh, svh, lvh Applicable for Hero sections, Mobile bottom navigation bars, chat interfaces, or modals
Common Pattern
You define a globals.css file which will contain the theme.
@layer theme {
:root {
/* Colors & Textures */
--ink-black: #1a1a1a;
--washi-paper: #f9f7f2;
--washi-texture: url("/4093px-texture.webp");
/* Fonts */
--font-sumi: "Geist Mono", monospace;
}
}
/* global.css*/
For every component, you write two files:
- component.module.css : You provide the default styling here.
.container {
/* Use the global DNA */
background-color: var(--washi-paper); /*Notice it pulls from global*/
font-family: var(--font-sumi);
/* Create a local "hook" for density */
opacity: var(--ink-density, 1);
/* Layout logic */
display: grid;
position: relative;
}
- component.tsx : The UI component’s layout goes here.
import styles from "./Panel.module.css";
export default function SumiPanel({ density = 1.0, children }) {
return (
<article className={styles.container} style={{ "--ink-density": density }}>
{children}
</article>
);
}