Sneaky CSS: Concepts and Features I Never Knew

I have been a full time frontend developer for over 3.5 years, and I am always learning new CSS syntax and concepts. I thought I would share some concepts and features I have only learned in the last year or so that have helped me style and build UIs faster and more easily.

Collapsing Margins

TIL about collapsing margins in CSS. This explains SO many of weird problems I've had over the years! 🤯
— Brian Mitchell (@BrianMitchL) March 30, 2020

I didn't notice this for a long time, but if you have a series of elements that have a margin-top and margin-bottom set, the browser uses the largest (when the values are positive) value for the margin between the elements. This concept, called margin collapsing, becomes important with spacing block elements, using borders, set height, or clearing floats. To help with preventing this from happening (and better component isolation), I've even seen the argument for Margin considered harmful. If you need to override collapsed margins you can add wrapper elements, use padding, or fixed height spacer elements.

Example

Each p has a margin-top of 0.25rem and margin-bottom of 1rem, but because the bottom is larger than the top, the resulting space is 1rem. Each p still has the margin set, but if the parent element does not have any padding or borders of its own, it collapses the margins for its height, and the margin of the children might be outside the parent.

See the code!


.collapsing-margins-example {
background-color: yellow;
color: black;
}
.collapsing-margins-example.add-padding {
padding: 0.5rem;
}
.collapsing-margins-example p {
margin-top: 0.25rem;
margin-bottom: 1rem;
background: lightsteelblue;
}
.collapsing-margins-example div {
margin-top: 2rem;
margin-bottom: 2rem;
background: lightsalmon;
}


This wrapper has no padding or border, so it's height uses the collapsed margins
of the first and last elements.


Paragraph
Paragraph
Paragraph


This wrapper has a padding of `0.5rem`, so it's height uses the margins of its
children.


Paragraph
Paragraph
Paragraph


Each `p` still has a `margin-top` of `0.25rem` and `margin-bottom` of `1rem`,
but we introduce a `div` which has a `margin-top` and `margin-bottom` of `2rem`.
Because the bottom margin on the `p` is smaller than the margin on the `div`, it
collapses, and the resulting space is the `2rem` from the `div`.


Paragraph
Paragraph

Paragraph
Paragraph

Paragraph


This wrapper has no padding or border, so it's height uses the collapsed margins of the first and last elements.

Paragraph

Paragraph

Paragraph

This wrapper has a padding of 0.5rem, so it's height uses the margins of its children.

Paragraph

Paragraph

Paragraph

Each p still has a margin-top of 0.25rem and margin-bottom of 1rem, but we introduce a div which has a margin-top and margin-bottom of 2rem. Because the bottom margin on the p is smaller than the margin on the div, it collapses, and the resulting space is the 2rem from the div.

Paragraph

Paragraph

Paragraph

Paragraph

Paragraph

Stacking Contexts

This isn't as new of a concept to me, but it's so important I think it's worth calling out. Have you ever been working on a site and come across z-indexes of hundreds or thousands? It's wild, right? I've found that once I understood stacking contexts and how positioning works with z-index and DOM order, I almost never needed to use a z-index above 1 or 2 (unless I needed to override or stack on top of a 3rd party library that had it set very high). I can't think of a better example than the one on the MDN page linked above, check it out!

focus-visible

The :focus-visible pseudo selector is an upcoming feature that lets you style the focus state of an element differently based on how the user is interacting with the web page (mouse/tap vs keyboard). This lets you create focus rings around elements only when using the Tab, Shift + Tab, or arrow keys, while keeping the page clean from focus rings when interacting with a mouse. Browser support is not universal yet, but there is a wonderful focus-visible polyfill maintained by the Web Incubator Community Group which provides the feature in the form of a class or data attribute that you can use as a selector instead.

Different browsers and OSes have different behavior for focusing buttons when clicking on them. How do you handle these differences? https://t.co/RVs1swlogE
— Brian Mitchell (@BrianMitchL) April 22, 2020

currentColor

The currentColor keyword is the value of the color property (you know, to set text color). This lets you use the value of color for other uses, like backgrounds, borders, shadows, etc. It becomes especially powerful when combined with things like setting border and SVG fill colors to match the text color of a button or other element that might change when a user interacts with it.

Example

Toggle the checkbox to see that all colors change, even though we set the color property in only one place.

See the code!


.currentcolor-example input ~ div {
color: orangered;
padding: 0.5rem;
}
.currentcolor-example input:checked ~ div {
color: rebeccapurple;
}



Toggle color

This color is set with CSS, and the border is set to currentColor.

The border-top that's set to currentColor, the same color as the parent
color!




Toggle color
This color is set with CSS, and the border is set to currentColor.
The border-top that's set to currentColor, the same color as the parent color!

position: sticky

Sticky positioning has been around for years, but IE11 doesn't (and will never) support it, so I hadn't ever actually used it. This lets you have an element flow as if it were relatively positioned until it reaches the edge of the containing block (through scrolling). From there it behaves more closely to fixed positioning, but only on the axis anchored with a top, left, right, or bottom property and within the bounds of its parent element. For the site I used it, we accepted that IE would have a slightly degraded experience by not supporting it, due to the difficulty of writing a replacement to sticky positioning with JavaScript for our scenario.

Accessibility with display: none

Also TIL, when styling labels that wrap radio inputs, _of course_ they won't be keyboard navigable if the inputs are set to `display: none`. (Absolutely position them behind the labels)
— Brian Mitchell (@BrianMitchL) November 12, 2019

While it seems obvious now, when I was looking at fixing keyboard and accessibility support for a custom checkbox-like input in an app, I realized that display: none all but removes an element from the document. It still exists in the DOM, and can be toggled through clicking on a linked label, but otherwise it's effectively not there. To avoid this from happening, I found that the following CSS is a great way to visually hide the input while keeping it visible for focus management, tabbing with a keyboard, and being spoken with screen readers.

position: absolute;
// use clip for legacy browsers
clip: rect(0, 0, 0, 0);
clip-path: circle(0);

Example

Note: Depending on your OS and browser, you may need to enable additional features for keyboard navigation. On Safari for macOS, you need to go to System Preferences > Keyboard > Shortcuts then select the "Use keyboard navigation to move focus between controls".

With a mouse, both checkboxes work the same, but if you use Tab to focus on them, you'll see that the first checkbox isn't focusable, while the second one is. This is despite the fact that they both visually look and work the same. An orangered border indicates that a checkbox is checked.

See the code!


.checkbox-example button {
margin: 0 0 0.5rem;
}
.checkbox-example input {
margin: 0;
padding: 0;
cursor: pointer;
}
.checkbox-example input + label {
transition: none;
display: block;
border: 0.25rem dotted lightseagreen;
padding: 0.25rem;
margin: 0 0 0.5rem;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
.checkbox-example input:focus + label {
outline: dodgerblue solid 0.25rem;
outline-offset: 0.25rem;
}
.checkbox-example input:checked + label {
border: 0.25rem solid orangered;
}
.checkbox-example input#checkbox-1 {
display: none;
}
.checkbox-example input#checkbox-2 {
position: absolute;
// use clip for legacy browsers
clip: rect(0, 0, 0, 0);
clip-path: circle(0);
}



Click me for focus

Custom checkbox with display: none

Custom checkbox with styling to hide the input


Click me for focus Custom checkbox with display: none Custom checkbox with styling to hide the input