Some Additional, Advanced CSS

It is vain to do with more what can be done with less.

William of Ockham

Our HTML/CSS focus up to this point has been relatively broad, to start with the basics. Here we want to sand down some of the rough edges, and introduce you to some specific, advanced techniques you can use to refine and enliven your work—still with just CSS, no JavaScript (yet)!

A good pattern to follow in web (and all) development is to use each technology only for what it does best—using HTML for semantic meaning, CSS to handle how we form a page, and, later, JavaScript to introduce more interaction. But even before we get to JS, we can start to layer in some more liveliness in our CSS.

Let’s look at some examples.

Overflows and Scrolling #

An overflow in CSS happens when there is too much content to fit in a container—usually because you have manually constrained its height or width. (By default, the browser will try to show you everything!)

We can use this behavior intentionally to crop our content or create scrolling areas:

Some of you have already discovered these!

Importantly, this creates a new stacking context—which means things with position (and some other properties) will now use the overflow container as their reference/origin:

text-overflow and -webkit-line-clamp #

You can also excerpt text (perhaps on a landing page) with the text-overflow (for a single line) or -webkit-line-clamp (for multiple lines) properties—which will add an ellipsis where the text overflows. Only do this when the full text is available on a subsequent page:

This -webkit stuff is hokey, but still how it is done!

Precise Text Positioning #

HTML renders a lot of extra space around text elements, called the line box (or, in design software parlance, the bounding box).

It is based on the font-family, the font-size, and the line-height, which basically means it is different all the time—and crucially, often different from Adobe/Figma to HTML. This makes it difficult to position type precisely—especially at large, expressive sizes like your headings! It’s always annoying, and you’ll often be adding/subtracting your spacing (margin or padding) to account for it, if you want to line everything up just right, optically.

Let’s avoid it. We can use pseudo-elements, ::after / ::before—which are entirely created by CSS, not in your DOM—to negate this vertical space with a negative margin. By doing this on the pseudo-elements, we can still position the parent element normally, otherwise:

Here we also move the text with margin-left and margin-right, though usually this adjustment is much more minor (to the point of ignoring).

Figma is actually ahead of CSS here with its recent Vertical Trim option. But the code for this kind of thing will get much easier in coming years with the analogous text-box-trim and text-box-edge properties! Soon.

Text Ragging (Kinda) #

We’ve gone on-and-on about how you can’t treat the web like print—always perfectly ragging your text for nice, smooth blocks. In modern (responsive) web design we don’t always know what our text will be, nor where it will wrap!

But we can do a handful of things to make for better ragging/wraps, given the unknowns—judiciously using hyphens / &shy;, <wbr>, <nobr> and &nbsp;, and balance to somewhat control your line breaks.

hyphens / &shy; #

The hyphens property allows long, multi-syllable words to be hyphenated when they wrap across multiple lines. This can be done automatically by the browser, or by manually inserting &shy; (for soft hyphen) as an HTML entity:

Note <html lang="en"> is needed for Chrome—as the auto property works from each browser’s different, internal (and usually, only English) dictionary—so this all has somewhat limited utility/reliability.

<wbr> #

Somewhat similar to &shy;, the <wbr> is a (void/empty) HTML element that denotes a word break opportunity—a bit like an optional <br>! You can use these to control where single long word will wrap, without a hyphen:

<nobr> and &nbsp; #

More often, you’ll want to keep certain words together—to avoid a widow or orphan, or to keep important/related text together—like in dates, November 8, or with names like van Zanten.

You can wrap multiple words (or whole phrases) in a <nobr> tag—keeping in mind that like <em> or <strong>, the default behavior is cleared by most resets (ours included)—so you have to restore the property in CSS.

You can also use a manual &nbsp; entity between words:

On a Mac, you can insert an encoded &‍nbsp; with  Space. (It’s seemingly much harder on Windows.) This works in many programs, not just your IDE! It’s harder to see, but easier to read.

text-wrap: balance; #

After many, many years of patient, typographic waiting (and some JS shenanigans) we now have growing browser support for “balancing” uneven line lengths with text-wrap: balance; :

This is particularly noticeable (and helpful) for centered text! There is also pretty, but Safari doesn’t support it yet.

Hanging Punctuation (Sorta) #

Ideally we could set punctuation outside of our text blocks, for visual alignment based solely on the letters—a traditional design technique called hanging punctuation. (There is actually a CSS property for this, but only Safari supports it!) But we can still approximate the behavior, at least for quotes:

Note that the quotes are not in the HTML in the last example!

When in doubt, The Elements of Typographic Style explains these conventions. But also, as Bringhurst says, “read the text before designing it.” Always put yourself in the mind of your reader!

Filters! #

CSS can apply visual effects on elements—adjusting their graphical display after they are laid out and rendered in the page—with the filter property:

Note that multiple filters are applied in sequencechanging the order changes the result.

These also correspond to backdrop-filter values—which apply the effect to the page behind an element! You’ll often use these in conjunction with opacity or a mix-blend-mode for interesting Photoshop/Figma-like layer-blending effects:

These are “hot rn.”

Transforms! #

Beyond our standard sizing and layout afforded by CSS, you can also visually manipulate elements using CSS transforms—scaling, skewing, translating, or rotating elements after they are laid out in the DOM. It’s like grabbing the “corner handles” in Adobe/Figma!

scale() / scaleX() / scaleY() / scaleZ() / scale3d()
Change the displayed size of the element—as if it is an image.
skew() / skewX() / skewY()
Tilt an element to the left or right, like turning a rectangle into a parallelogram.
translate() / translateX() / translateY() / translate3d()
Move an element left/right and up/down, and also in three-dimensional space.
rotate() / rotate3d()
Rotate the element.
perspective()
Doesn’t affect the element itself, sets the distance between the user and the three-dimensional plane.

The units for these are all a bit different; MDN is your friend here, as usual. You can apply single or multiple transforms, which are written space-separated and applied one after the other:

		.rotated {
	transform: rotate(-5deg);
}

	
		.rotated-and-scaled {
	transform: rotate(-5deg) scale(120%);
}

	

Keep in mind that these transformations are applied after the rest of the CSS is parsed, and thus treat your element a bit like an image. And like overflow, above, transform also creates a new stacking context for its children:

Note how the elements don’t take up more space in the document flow/layout—but they do cause an overflow!

You shouldn’t use transform for layout—as in, don’t use translate when margin, padding, flex, or grid can achieve your layout. This is bad practice, and usually very brittle! Especially when working responsively.

Use transform only for what other properties can’t accomplish!

Transitions! #

CSS transitions allow us to move nicely between CSS property values.

Instead of having a property take effect immediately when a pseudo-class is applied (or later, and more commonly, with JSa proper class), we can tell a CSS property to transition from one value to another over a given amount of time (duration), and with a specific acceleration (timing-function), or a delay. Motion can quickly get very complex!

You’ll often see a transition in shorthand:

		.some-cool-transition {
	transition: all 2s 1s linear;
}

	
		.some-cool-transition {
	transition-delay: 1s;
	transition-duration: 2s;
	transition-property: all;
	transition-timing-function: linear;
}

	

You can also control how different properties of an element transition independently, with a comma-separated list:

		.some-cool-transition {
	transition: background-color 2s linear, transform 1s ease-in-out;
}

	
		.some-cool-transition {
	transition-duration: 2s, 1s;
	transition-property: background-color, transform;
	transition-timing-function: linear, ease-in-out;
}

	

Sometimes the shorthand here is easier than discrete properties, where you have to maintain the same order across all of them. It’s all the same to the computer!

Often, CSS transitions will be used with JavaScript when adding/removing classes, to make a state change less abrupt. For now, we’ll use pseudo-classes to demonstrate:

You can get even more control over the easing with a custom curve function.

Nearly all CSS properties can be transitioned—but keep in mind that changes that cause a reflow (re-triggering layout, sometimes called paint) are slow and can make your page feel glitchy—especially when you start having many of them. Each in-between state causes the browser to re-render your entire document! So stick to changes of color, opacity, and transform for the smoothest performance.

And Animations! #

Sometimes, transitioning a property from one value to another isn’t enough—you may need more complicated (or repeating) motion behavior. CSS animations allow precise state sequencing with @keyframes (akin to… keyframes or a timeline in other software contexts).

To create a keyframe animation, we define an element’s initial state in CSS—then an animation property, which includes timing and behavior, as well as an animation name (something that you make up). Again, you’ll often see these in shorthand:

		section {
	animation: blinking 3s infinite ease-in-out;
}

	
		section {
	animation-duration: 3s;
	animation-iteration-count: infinite;
	animation-name: blinking;
	animation-timing-function: ease-in-out;
}

	

Importantly, we then define the actual keyframes of an animation in a separate at-rule. Each keyframe is specified with a percentage of the animation’s duration, and can specify multiple properties—a bit like selectors for the time:

Don’t go overboard! A little animation goes a long way.

With great power there must also come great responsibility.

Stan Lee