3 min read
CSS snippets
Small CSS patterns I keep coming back to: the dotted line, line clamping, fluid type and handy :has() selectors.
- css
A few small pieces of CSS that I reuse across projects, with the reasoning behind each one.
Dotted line
A dotted rule made from a gradient: half of every 6px is the colour and half is transparent, so it draws crisp dashes at any width and takes its colour from a custom property. It's the line running down both sides of this site.
.dots-x {
height: 1px;
background-image: linear-gradient(to right, var(--dash-color, currentColor) 50%, transparent 0);
background-size: 6px 1px;
}
.dots-y {
width: 1px;
background-image: linear-gradient(to bottom, var(--dash-color, currentColor) 50%, transparent 0);
background-size: 1px 6px;
}<div class="dots-x" style="--dash-color: #d4d4d8"></div>Line clamp
Cuts text off after a set number of lines and adds an ellipsis. It needs the old -webkit-box display to work, and the property is supported in every modern browser.
.clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
}Don't override `display`
The clamp only works while display is -webkit-box. If a later rule sets display: block on the same element, the clamp silently stops working and the text grows to its full length.
Fluid type
clamp(min, preferred, max) lets text scale with the screen but never go below or above a size. The simple version uses a viewport unit as the preferred value:
h1 {
font-size: clamp(2.5rem, 8vw, 4rem);
}To make a size grow evenly between two screen widths, work out a slope and an offset. This one goes from 16px on a 320px screen to 32px on a 1200px screen:
p {
font-size: clamp(1rem, 0.636rem + 1.818vw, 2rem);
}The maths is slope = (max - min) / (maxScreen - minScreen) for the vw part, and offset = min - slope × minScreen for the fixed part.
Handy :has() selectors
:has() styles an element based on what's inside it, which used to need JavaScript.
/* A label reacts to its own input */
label:has(input:checked) {
color: var(--fg);
}
label:has(input:focus-visible) {
outline: 2px solid var(--accent);
}
/* Mark a field once its input is invalid and has been typed in */
.field:has(input:invalid:not(:placeholder-shown)) {
border-color: crimson;
}
/* Change a card's layout only when it has an image */
.card:has(img) {
grid-template-columns: 8rem 1fr;
}
/* Stop the page scrolling behind an open dialog */
body:has(dialog[open]) {
overflow: hidden;
}
/* Use two columns once a list has four or more items */
ul:has(> li:nth-child(4)) {
columns: 2;
}