::marker
section of the article. The built-in list markers are bullets, ordinal numbers, and letters. The ::marker
pseudo-element …
Everything You Need to Know About the Gap After the List Marker originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>::marker
section of the article. The built-in list markers are bullets, ordinal numbers, and letters. The ::marker
pseudo-element allows us to style these markers or replace them with a custom character or image.
::marker {
content: url('/marker.svg') ' ';
}
The example that caught my attention uses an SVG icon as a custom marker for the list items. But there’s also a single space character (" "
) in the CSS value next to the url()
function. The purpose of this space seems to be to insert a gap after the custom marker.
When I saw this code, I immediately wondered if there was a better way to create the gap. Appending a space to content
feels more like a workaround than the optimal solution. CSS provides margin
and padding
and other standard ways to space out elements on the page. Could none of these properties be used in this situation?
First, I tried to substitute the space character with a proper margin:
::marker {
content: url('/marker.svg');
margin-right: 1ch;
}
This didn’t work. As it turns out, ::marker
only supports a small set of mostly text-related CSS properties. For example, you can change the font-size
and color
of the marker, and define a custom marker by setting content
to a string or URL, as shown above. But the margin
and padding
properties are not supported, so setting them has no effect. What a disappointment.
Could it really be that a space character is the only way to insert a gap after a custom marker? I needed to find out. As I researched this topic, I made a few interesting discoveries that I’d like to share in this article.
First, let’s confirm what margin
and padding
do on the <ul>
and <li>
elements. I’ve created a test page for this purpose. Drag the relevant sliders and observe the effect on the spacing on each side of the list marker. Tip: Use the Reset button liberally to reset all controls to their initial values.
Note: Browsers apply a default padding-inline-left
of 40px
to <ol>
and <ul>
elements. The logical padding-inline-left
property is equivalent to the physical padding-left
property in writing systems with a left-to-right inline direction. In this article, I’m going to use physical properties for the sake of simplicity.
As you can see, padding-left
on <li>
increases the gap after the list marker. The other three properties control the spacing to the left of the marker, in other words, the indentation of the list item.
Notice that even when the list item’s padding-left
is 0px
, there is still a minimum gap after the marker. This gap cannot be decreased with margin
or padding
. The exact length of the minimum gap depends on the browser.
To sum up, the list item’s content is positioned at a browser-specific minimum distance from the marker, and this gap can be further increased by adding a padding-left
to <li>
.
Next, let’s see what happens when we position the marker inside the list item.
The list-style-position
property accepts two keywords: outside
, which is the default, and inside
, which moves the marker inside the list item. The latter is useful for creating designs with full-width list items.
If the marker is now inside the list item, does this mean that padding-left
on <li>
no longer increases the gap after the marker? Let’s find out. On my test page, turn on list-style-position: inside
via the checkbox. How are the four padding
and margin
properties affected by this change?
As you can see, padding-left
on <li>
now increases the spacing to the left of the marker. This means that we’ve lost the ability to increase the gap after the marker. In this situation, it would be useful to be able to add margin-right
to the ::marker
itself, but that doesn’t work, as we’ve established above.
Additionally, there’s a bug in Chromium that causes the gap after the marker to triple after switching to inside
positioning. By default, the length of the gap is about one-third of the text size. So at a default font-size
of 16px
, the gap is about 5.5px
. After switching to inside
, the gap grows to the full 16px
in Chrome. This bug affects the disc
, circle
, and square
markers, but not ordinal number markers.
The following image shows the default rendering of outside and inside-positioned list markers across three major browsers on macOS. For your convenience, I’ve horizontally aligned all list items on their markers to make it easier to compare the differences in gap sizes.
To sum up, switching to list-style-position: inside
introduces two problems. We can no longer increase the gap via padding-left
on <li>
, and the gap size is inconsistent between browsers.
Finally, let’s see what happens when we replace the default list marker with a custom marker.
There are two ways to define a custom marker:
list-style-type
and list-style-image
propertiescontent
property on the ::marker
pseudo-elementThe content
property is more powerful. For example, it allows us to use the counter()
function to access the list item’s ordinal number (the implicit list-item
counter) and decorate it with custom strings.
Unfortunately, Safari doesn’t support the content
property on ::marker
yet (WebKit bug). For this reason, I’m going to use the list-style-type
property to define the custom marker. You can still use the ::marker
selector to style the custom marker declared via list-style-type
. That aspect of ::marker
is supported in Safari.
Any Unicode character can potentially serve as a custom list marker, but only a small set of characters actually have “Bullet” in their official name, so I thought I’d compile them here for reference.
Character | Name | Code point | CSS keyword |
---|---|---|---|
• | Bullet | U+2022 | disc |
‣ | Triangular Bullet | U+2023 | |
⁃ | Hyphen Bullet | U+2043 | |
⁌ | Black Leftwards Bullet | U+204C | |
⁍ | Black Rightwards Bullet | U+204D | |
◘ | Inverse Bullet | U+25D8 | |
◦ | White Bullet | U+25E6 | circle |
☙ | Reversed Rotated Floral Heart Bullet | U+2619 | |
❥ | Rotated Heavy Black Heart Bullet | U+2765 | |
❧ | Rotated Floral Heart Bullet | U+2767 | |
⦾ | Circled White Bullet | U+29BE | |
⦿ | Circled Bullet | U+29BF |
Note: The CSS square
keyword does not have a corresponding “Bullet” character in Unicode. The character that comes closest is the Black Small Square (▪️) emoji (U+25AA
).
Now let’s see what happens when we replace the default list marker with list-style-type: "•"
(U+2022
Bullet). This is the same character as the default bullet, so there shouldn’t be any major rendering differences. On my test page, turn on the list-style-type
option and observe any changes to the marker.
As you can see, there are two significant changes:
font-size
.According to CSS Counter Styles Level 3, the default list marker (disc
) should be “similar to • U+2022
BULLET”. It seems that browsers increase the size of the default bullet to make it more legible. Firefox even uses a special font, -moz-bullet-font
, for the marker.
Can the small size problem be fixed with CSS? On my test page, turn on marker styling and observe what happens when you change the font-size
, line-height
, and font-family
of the marker.
As you can see, increasing the font-size
causes the custom marker to become vertically misaligned, and this cannot be corrected by decreasing the line-height
. The vertical-align
property, which could easily fix this problem, is not supported on ::marker
.
But did you notice that changing the font-family
can cause the marker to become bigger? Try setting it to Tahoma
. This could potentially be a good-enough workaround for the small-size problem, although I haven’t tested which font works best across the major browsers and operating systems.
You may also have noticed that the Chromium bug doesn’t occur anymore when you position the marker inside the list item. This means that a custom marker can serve as a workaround for this bug. And this leads me to the main problem, and the reason why I started researching this topic. If you define a custom marker and position it inside the list item, there is no gap after the marker and no way to insert a gap by standard means.
::marker
doesn’t support padding
or margin
.padding-left
on <li>
doesn’t increase the gap, since the marker is positioned inside
.Here’s a summary of all the key facts that I’ve mentioned in the article:
padding-inline-start
of 40px
to <ul>
and <ol>
elements.disc
, decimal
, etc.). There is no minimum gap after custom markers (string or URL).padding-left
to <ul>
, but only if the marker is positioned outside the list item (the default mode).font-family
on ::marker
can increase their size.Looking back at the code example from the beginning of the article, I think I understand now why there’s a space character in the content
value. There is just no better way to insert a gap after the SVG marker. It’s a workaround that is needed because no amount of margin
and padding
can create a gap after a custom marker that is positioned inside the list item. A margin-right
on ::marker
could easily do it, but that is not supported.
Until ::marker
adds support for more properties, web developers will often have no choice but to hide the marker and emulate it with a ::before
pseudo-element. I had to do that myself recently because I couldn’t change the marker’s background-color
. Hopefully, we won’t have to wait too long for a more powerful ::marker
pseudo-element.
Everything You Need to Know About the Gap After the List Marker originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>How I Added Scroll Snapping To My Twitter Timeline originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Scroll snapping is probably most associated with horizontal carousels (see Chris’s CSS-only approach) and particular web pages divided into full-screen slides. But why stop there? I believe that snapping can improve the scrolling experience on any web page that lays out items in a grid or feed.
For example, most shopping websites show products in a grid. Ideally, the user would like to jump between the grid rows with minimal effort. The user can press Space to scroll the page by roughly one screen (viewport height), but depending on the height of the grid rows, the scroll position will eventually get “out of sync” with the grid, and the user will have to re-adjust it manually.
If we add scroll snapping to this page, the user can consistently scroll to the next row with the Space key (pressing Shift + Space will scroll to the previous row). It’s pretty effortless.
I think that scroll snapping would be a welcome addition to this website. And it’s not even that complicated to implement. The CSS code that I used for this example is relatively simple:
html {
scroll-snap-type: y proximity;
}
.product-item {
scroll-snap-align: start;
scroll-margin-top: 75px; /* height of web page’s sticky header */
}
You don’t have to wait if a website you visit regularly hasn’t yet added scroll snapping and you think it would improve your scrolling experience. You can add scroll snapping yourself — with user styles.
In the video above, you can see that I selected a user.css file in Safari’s advanced preferences. This file is a user style sheet. It contains CSS styles that I’ve written, stored in a local .css
file, and added to Safari. These “user styles” are then applied to every web page I open in Safari.
Chrome and Firefox do not allow users to select a user style sheet. Firefox supported a similar feature called userContent.css
in the past, but that feature was deprecated and disabled by default in 2019. I recommend the Stylus browser extension for these two browsers (and other Chromium-based browsers).
One significant advantage of Stylus is that it allows you to write user styles for specific websites and URLs. Safari’s user style sheet applies to all websites, but this can be worked around, e.g., by using the new :has()
pseudo-class to create selectors that only match specific websites.
The CSS Cascading module defines a User Origin for styles the user adds. Safari’s user style sheet belongs to this origin, but the Stylus extension injects user styles to the Author Origin, where the website’s style sheets live. Specifically, Stylus inserts user styles directly to the page via a <style>
element at the end of <html>
which makes it the final style sheet on the page. Technically, this means styles added via Stylus are classified as author styles since they’re not in the User Origin, but I will continue to call them user styles because the user adds them.
However, it’s worth keeping this distinction in mind because it affects the cascade. When selector specificity is equal, a real user style is weaker than the page’s own style. This makes user styles an excellent fit for user defaults. Under the same conditions, a style added via Stylus is stronger than the page‘s style, so Stylus cannot as easily be used to define user defaults.
If we add !important
to the mix, both real user styles and styles added via Stylus are stronger than the page’s styles. So when you want to impose your user styles on a website, it doesn’t matter if you use Safari’s “Style sheet” option or the Stylus extension. Your !important
styles will win either way.
In the next section, I will use a set of !important
user styles to enforce scroll snapping on the timeline page of Twitter’s website. My goal is to speed up the process of reading my Twitter timeline by avoiding awkward scroll positions where the topmost tweet is only partially on screen.
After some experimentation, I’ve settled on the following CSS code. These styles work well in Firefox, but I’ve experienced some issues in Chrome and Safari. I will describe these issues in more detail later in the article, but for now, let’s focus on the behavior in Firefox.
html {
scroll-snap-type: y mandatory !important;
}
/* tweets in the timeline are <article> elements */
article {
scroll-snap-align: start !important;
}
/* un-stick the sticky header and make it “snappable” as well */
[aria-label="Home timeline"] > :first-child {
position: static !important;
scroll-snap-align: start !important;
}
/* hide the “new Tweets available” floating toast notification */
[aria-label="New Tweets are available."] {
display: none !important;
}
It is necessary to add !important
to each declaration because all the user styles must win over the web page’s own styles for our custom scroll snapping implementation to work correctly. I wish that instead of repeatedly writing !important
, I could just put my user styles in an “important layer,” but such a CSS feature does not exist (yet).
Watch the video below to see my scroll snap user styles in action. Notice how each press on the Space key scrolls the next set of tweets into view, and the first tweet of each set is aligned to the top edge of the viewport. This allows me to read my timeline more quickly. When I need to go back to the previous set of tweets, I can press Shift + Space.
What I like about this type of scroll snapping is that it allows me to predict how far the page will scroll whenever I press Space. Each scroll distance equals the combined heights of the visible tweets that are entirely on the screen. In other words, the partially visible tweet at the bottom of the screen will move to the top of the screen, which is precisely what I want.
Space
will scroll Dave’s tweet to the top of the screen.To try out my scroll snap user styles on your own Twitter timeline, follow these steps:
My implementation of scroll snapping for Twitter’s timeline has one major flaw. If a tweet is taller than the viewport, it is impossible to scroll the page to reveal the bottom part of that tweet (e.g., if you want to like or retweet that tweet) because the browser forcefully snaps the page to show the top of the tweet (or the top of the following tweet).
The severity of this problem depends on the user’s display. Viewing Twitter’s timeline on a large desktop monitor at a small page zoom factor, you may not encounter any tweets taller than the viewport.
I have asked the CSS Working Group if it would be possible to add a mechanism allowing the user to override the browser’s mandatory scroll snapping. I should probably mention that this problem could, at least in theory, be resolved by switching from mandatory
to proximity
snapping. I’ve tested proximity
snapping in Chrome and Firefox, and I found it inconsistent and confusing. The browser would often snap when I didn’t expect it to, and vice versa. Maybe Twitter’s code is interfering with the proximity
algorithm, the browsers are still a bit buggy, or perhaps I’m just “scrolling it wrong,” if that’s even possible. I don’t know.
But the main reason why I went with mandatory
snapping is that I wanted to avoid situations where the topmost tweet is only partially on screen after a scroll. The type of fast-scrolling between sets of tweets that I’ve shown in the video above is only possible with mandatory
snapping.
If you, like me, prefer mandatory
snapping, I can suggest the following two workarounds for the “tall tweet” problem:
My scroll snap user styles produce noticeably different scroll snapping behaviors in Chrome, Safari, and Firefox. Those differences are in part since the exact implementation of the snapping mechanism is left up to the browser:
The CSS Scroll Snap Module intentionally does not specify nor mandate any precise animations or physics used to enforce snap positions; this is left up to the user agent.
The current version of Safari has a bug that prevents scroll snapping from working correctly on the Twitter timeline. I have reported this bug.
In Chrome, I have encountered the following problems:
These browser bugs and differences between browsers can be a problem for websites considering implementing scroll snapping. For example, a web developer might hold back because they don’t like how scroll snapping behaves in one particular browser. Browsers can mitigate this problem by becoming more interoperable. In fact, Scroll Snap is one of the areas of focus of the cross-browser Interop 2022 effort.
Another way the situation could be improved is by introducing new CSS properties that would make scroll snapping more configurable. This could include the duration of the snapping animation, the length of the proximity threshold for snapping, and a mechanism to override mandatory snapping.
I’ve been using my scroll snap user styles on Twitter’s timeline for a couple of weeks, and I don’t want to go back. The ability to quickly flip through my feed with only the Space key is just on another level.
However, I consider this an advanced feature that probably isn’t for everyone. There’s a reason why I’ve enabled it only on the timeline (/home
path) and nowhere else on Twitter’s website. Snapping is a significant change in how the page scrolls, and it takes some time to get used to. It can work great for a specific use case, but it can also get in the way and frustrate the user.
Websites with feeds should therefore consider offering scroll snapping only as an optional feature, after careful consideration and plenty of testing in different browsers and with different input methods (mouse, keyboard, trackpad, touch screen, etc.).
Finally, I highly recommend installing and trying out the Stylus browser extension. Web developers (or anyone who knows CSS) have the power to style any website in their browser. You can apply minor improvements and fixes to your favorite websites. I mostly use it to hide page elements that I find annoying, such as sticky headers, video pop-ups, and vote counts.
But more importantly, Stylus allows you to quickly test new CSS features on any website and report browser bugs, if necessary. By doing this, you can help make the web platform a little better.
How I Added Scroll Snapping To My Twitter Timeline originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>text-decoration-thickness
and text-underline-offset
, two relatively new and widely-supported CSS properties that give us more control over the styling of underlines.
Let me demonstrate the usefulness of …
When to Avoid the text-decoration Shorthand Property originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>text-decoration-thickness
and text-underline-offset
, two relatively new and widely-supported CSS properties that give us more control over the styling of underlines.
Let me demonstrate the usefulness of text-decoration-thickness
on a simple example. The Ubuntu web font has a fairly thick default underline. We can make this underline thinner like so:
:any-link {
text-decoration-thickness: 0.08em;
}
/explanation Throughout this article, I will use the :any-link
selector instead of the a
element to match hyperlinks. The problem with the a
tag as a selector is that it matches all <a>
elements, even the ones that don’t have a href
attribute and thus aren’t hyperlinks. The :any-link
selector only matches <a>
elements that are hyperlinks. Web browsers also use :any-link
instead of a
in their user agent stylesheets.
Many websites, including Google Search and Wikipedia, remove underlines from links and only show them when the user hovers a link. Removing underlines from links in body text is not a good idea, but it can make sense in places where links are more spaced apart (navigation, footer, etc.). With that being said, here’s a simple implementation of hover underlines for links in the website’s header:
header :any-link {
text-decoration: none;
}
header :any-link:hover {
text-decoration: underline;
}
But there’s a problem. If we tested this code in a browser, we’d notice that the underlines in the header have the default thickness, not the thinner style that we declared earlier. Why did text-decoration-thickness
stop working after we added hover underlines?
Let’s look at the full CSS code again. Can you think of a reason why the custom thickness
doesn’t apply to the hover underline?
:any-link {
text-decoration-thickness: 0.08em;
}
header :any-link {
text-decoration: none;
}
header :any-link:hover {
text-decoration: underline;
}
The reason for this behavior is that text-decoration
is a shorthand property and text-decoration-thickness
its associated longhand property. Setting text-decoration
to none
or underline
has the side effect of re-initializing the other three text decoration components (thickness
, style
, and color
). This is defined in the CSS Text Decoration module:
The
text-decoration
property is a shorthand for settingtext-decoration-line
,text-decoration-thickness
,text-decoration-style
, andtext-decoration-color
in one declaration. Omitted values are set to their initial values.
You can confirm this in the browser’s DevTools by selecting one of the hyperlinks in the DOM inspector and then expanding the text-decoration
property in the CSS pane.
In order to get text-decoration-thickness
to work on hover underlines, we’ll have to make a small change to the above CSS code. There are actually multiple ways to achieve this. We could:
text-decoration-thickness
after text-decoration
,text-decoration
shorthand, ortext-decoration-line
instead of text-decoration
.Our first thought might be to simply repeat the text-decoration-thickness
declaration in the :hover
state. It’s a quick and simple fix that indeed works.
/* OPTION A */
header :any-link {
text-decoration: none;
}
header :any-link:hover {
text-decoration: underline;
text-decoration-thickness: 0.08em; /* set thickness again */
}
However, since text-decoration
is a shorthand and text-decoration-thickness
is its associated longhand, there really should be no need to use both at the same time. As a shorthand, text-decoration
allows setting both the underline itself and the underline’s thickness, all in one declaration.
/* OPTION B */
header :any-link {
text-decoration: none;
}
header :any-link:hover {
text-decoration: underline 0.08em; /* set both line and thickness */
}
If this code looks unfamiliar to you, that could be because the idea of using text-decoration
as a shorthand is relatively new. This property was only subsequently turned into a shorthand in the CSS Text Decoration module. In the days of CSS 2, text-decoration
was a simple property.
Unfortunately, Safari still hasn’t fully caught up with these changes. In the WebKit browser engine, the shorthand variant of text-decoration
remains prefixed (-webkit-text-decoration
), and it doesn’t support thickness
values yet. See WebKit bug 230083 for more information.
This rules out the text-decoration
shorthand syntax. The above code won’t work in Safari, even if we added the -webkit-
prefix. Luckily, there’s another way to avoid repeating the text-decoration-thickness
declaration.
When text-decoration
was turned into a shorthand, a new text-decoration-line
longhand was introduced to take over its old job. We can use this property to hide and show the underline without affecting the other three text decoration components.
/* OPTION C */
header :any-link {
text-decoration-line: none;
}
header :any-link:hover {
text-decoration-line: underline;
}
Since we’re only updating the line
component of the text-decoration
value, the previously declared thickness
remains intact. I think that this is the best way to implement hover underlines.
Keep in mind that when you set a shorthand property, e.g., text-decoration: underline
, any missing parts in the value are re-initialized. This is also why styles such as background-repeat: no-repeat
are undone if you set background: url(flower.jpg)
afterwards. See the article “Accidental CSS Resets” for more examples of this behavior.
When to Avoid the text-decoration Shorthand Property originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>text-decoration-thickness
and text-underline-offset
properties, and I want to share them with you here in this article.
Table of Contents
…
CSS Underlines Are Too Thin and Too Low in Chrome originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>text-decoration-thickness
and text-underline-offset
properties, and I want to share them with you here in this article.
First, let’s acknowledge one thing:
Let’s add a text link to a plain web page, set its font-family
to Arial, and compare the underlines across browsers and operating systems.
As you can see, the default underline is inconsistent across browsers. Each browser chooses their own default thickness and vertical position (offset from the baseline) for the underline. This is in line with the CSS Text Decoration module, which specifies the following default behavior (auto
value):
The user agent chooses an appropriate thickness for text decoration lines. […] The user agent chooses an appropriate offset for underlines.
There are two new, widely supported CSS properties that allow us to precisely define the thickness and offset for our underlines:
With these properties, we can create consistent underlines even across two very different browsers, such as the Gecko-based Firefox on Android and the WebKit-based Safari on macOS.
h1 {
text-decoration: underline;
text-decoration-thickness: 0.04em;
text-underline-offset: 0.03em;
}
Note: The text-decoration-thickness
property also has a special from-font
value that instructs browsers to use the font’s own preferred underline width, if available. I tested this value with a few different fonts, but the underlines were inconsistent.
OK, so let’s move on to the two Chrome bugs I noted earlier.
If you set the text-decoration-thickness
property to a font-relative length value that computes to a non-integer pixel value, Chrome will “floor” that value instead of rounding it to the nearest integer. For example, if the declared thickness is 0.06em
, and that computes to 1.92px
, Chrome will paint a thickness of 1px
instead of 2px
. This issue is limited to macOS.
a {
font-size: 2em; /* computes to 32px */
text-decoration-thickness: 0.06em; /* computes to 1.92px */
}
In the following screenshot, notice how the text decoration lines are twice as thin in Chrome (third row) than in Safari and Firefox.
For more information about this bug, see Chromium issue #1255280.
The text-underline-offset
property allows us to precisely set the distance between the alphabetic baseline and the underline (the underline’s offset from the baseline). Unfortunately, this feature is currently not implemented correctly in Chrome and, as a result, the underline is positioned too low.
h1 {
text-decoration: underline;
text-decoration-color: #f707;
/* disable “skip ink” */
-webkit-text-decoration-skip: none; /* Safari */
text-decoration-skip-ink: none;
/* cover the entire descender */
text-decoration-thickness: 0.175em; /* descender height */
text-underline-offset: 0; /* no offset from baseline */
}
Because of this bug, it is not possible to move the underline all the way up to the baseline in Chrome.
For more information about this bug, see Chromium issue #1172623.
Note: As you might have noticed from the image above, Safari draws underlines on top of descenders instead of beneath them. This is a WebKit bug that was fixed very recently. The fix should ship in the next version of Safari.
The two new CSS properties for styling underlines are a welcome addition to CSS. Hopefully, the two related Chrome bugs will be fixed sooner rather than later. If these CSS features are important to you, make your voice heard by starring the bugs in Chromium’s bug tracker.
CSS Underlines Are Too Thin and Too Low in Chrome originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]><b>
and <strong>
elements in browsers today is not very compatible with the wide range of …
Firefox’s `bolder` Default is a Problem for Variable Fonts originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]><b>
and <strong>
elements in browsers today is not very compatible with the wide range of font-weight
values enabled by variable fonts.
font-weight
of <b>
The purpose of the <b>
and <strong>
elements is to draw attention to a specific word or span of text on the page. Browsers make these elements stand out by increasing their font-weight
. This works well under normal conditions. For example, MDN Web Docs uses <b>
in a few places in the “Found a problem?” card at the bottom of each page.
Things become more complicated when the text on the page has a custom font-weight
. The default weight of text is 400
, but the font-weight
property accepts any number between 1
and 1000
(inclusive). Let’s take a look at how Chrome and Firefox render text wrapped in <b>
by default depending on the font-weight
of the surrounding text.
Chrome and Firefox disagree on the default rendering of <b>
elements. Chrome uses a constant font-weight
of 700
(Safari behaves the same), while Firefox chooses between three values (400
, 700
, and 900
) depending on the font-weight
of the surrounding text.
As you might have guessed, Chrome and Firefox use different font-weight
values for the <b>
and <strong>
elements in their user agent stylesheets.
/* Chrome and Safari’s user agent stylesheet */
strong, b {
font-weight: bold;
}
/* Firefox’s user agent stylesheet */
strong, b {
font-weight: bolder;
}
The bold
and bolder
values are specified in the CSS Fonts module; bold
is equivalent to 700
, while bolder
is a relative weight that is calculated as follows:
If the outer text has a font-weight of… | the bolder keyword computes to… |
---|---|
1 to 349 | 400 |
350 to 549 | 700 |
550 to 899 | 900 |
900 to 1000 | No change (same value as outer text) |
Chrome and Firefox disagree on the default rendering of <b>
, but which browser follows the standards more closely? The font-weight
property itself is defined in the CSS Fonts module, but the suggested font-weight
values for different HTML elements are located in the Rendering section of the HTML Standard.
/* The HTML Standard suggests the following user agent style */
strong, b {
font-weight: bolder;
}
The HTML Standard started suggesting bolder
instead of bold
all the way back in 2012. As of today, only Firefox follows this recommendation. Chrome and Safari have not made the switch to bolder
. Because of this inconsistency, the popular Normalize base stylesheet has a CSS rule that enforces bolder
across browsers.
There are two different defaults in browsers, and Firefox’s default matches the standard. So, should Chrome align with Firefox, or is Chrome’s default the better one? Let’s take another look at the default rendering of the <b>
element.
Each of the two defaults has a weak spot: Chrome’s bold
default breaks down at higher font-weight
values (around 700
), while Firefox’s bolder
default has a problem with lower font-weight
values (around 300
).
In the worst-case scenario for Firefox, text wrapped in <b>
becomes virtually indiscernible. The following screenshot shows text at a font-weight
of 349
in Firefox. Can you spot the single word that is wrapped in <b>
? Firefox renders this element at a default font-weight
of 400
, which is an increase of only 51 points.
If you use thin fonts or variable fonts at font-weight
values below 350
, be aware that the <b>
and <strong>
elements may not always be discernible in Firefox by default. In this case, it is probably a good idea to manually define a custom font-weight
for <b>
and <strong>
instead of relying on the browser’s sub-optimal default, which insufficiently increases the font-weight
of these elements.
/* Defining the regular and bold font-weight at the same time */
body {
font-weight: 340;
}
b,
strong {
font-weight: 620;
}
The bolder
value is outdated and doesn’t work well with variable fonts. Ideally, text wrapped in <b>
should be easy to spot regardless of the font-weight
of the surrounding text. Browsers could achieve that by always increasing the font-weight
by the same or a similar amount.
On that note, there is a discussion in the CSS Working Group about allowing percentages in font-weight
in the same manner as in font-size
. Lea Verou writes:
A far more common use case is when we want a bolder or lighter stroke than the surrounding text, in a way that’s agnostic to the weight of the surrounding text.
/* Increasing font-size by 100% */
h1 {
font-size: 200%;
}
/* PROPOSAL - Increasing font-weight by 50% */
strong, b {
font-weight: 150%;
}
Taking variable fonts into account, a value like 150%
would probably be a better default than the existing bold
/bolder
defaults in browsers today.
Firefox’s `bolder` Default is a Problem for Variable Fonts originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>--my-property
), custom elements (<my-element>
), and custom events (new CustomEvent('myEvent')
). At one point, we might even get custom media …
Custom State Pseudo-Classes in Chrome originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>--my-property
), custom elements (<my-element>
), and custom events (new CustomEvent('myEvent')
). At one point, we might even get custom media queries (@media (--my-media)
).
But that’s not all! You might have missed it because it wasn’t mentioned in Google’s “New in Chrome 90” article (to be fair, declarative shadow DOM stole the show in this release), but Chrome just added support for yet another “custom” feature: custom state pseudo-classes (:--my-state
).
Before talking about custom states, let’s take a quick look at the built-in states that are defined for built-in HTML elements. The CSS Selectors module and the “Pseudo-classes” section of the HTML Standard specify a number of pseudo-classes that can be used to match elements in different states. The following pseudo-classes are all widely supported in today’s browsers:
User action | |
---|---|
:hover |
the mouse cursor hovers over the element |
:active |
the element is being activated by the user |
:focus |
the element has the focus |
:focus-within |
the element has or contains the focus |
Location | |
:visited |
the link has been visited by the user |
:target |
the element is targeted by the page URL’s fragment |
Input | |
:disabled |
the form element is disabled |
:placeholder-shown |
the input element is showing placeholder text |
:checked |
the checkbox or radio button is selected |
:invalid |
the form element’s value is invalid |
:out-of-range |
the input element’s value is outside the specificed range |
:-webkit-autofill |
the input element has been autofilled by the browser |
Other | |
:defined |
the custom element has been registered |
Note: For brevity, some pseudo-classes have been omitted, and some descriptions don’t mention every possible use-case.
Like built-in elements, custom elements can have different states. A web page that uses a custom element may want to style these states. The custom element could expose its states via CSS classes (class
attribute) on its host element, but that’s considered an anti-pattern.
Chrome now supports an API for adding internal states to custom elements. These custom states are exposed to the outer page via custom state pseudo-classes. For example, a page that uses a <live-score>
element can declare styles for that element’s custom --loading
state.
live-score {
/* default styles for this element */
}
live-score:--loading {
/* styles for when new content is loading */
}
--checked
state to a <labeled-checkbox>
elementThe Custom State Pseudo Class specification contains a complete code example, which I will use to explain the API. The JavaScript portion of this feature is located in the custom element‘s class definition. In the constructor, an “element internals” object is created for the custom element. Then, custom states can be set and unset on the internal states
object.
Note that the ElementInternals
API ensures that the custom states are read-only to the outside. In other words, the outer page cannot modify the custom element’s internal states.
class LabeledCheckbox extends HTMLElement {
constructor() {
super();
// 1. instantiate the element’s “internals”
this._internals = this.attachInternals();
// (other code)
}
// 2. toggle a custom state
set checked(flag) {
if (flag) {
this._internals.states.add("--checked");
} else {
this._internals.states.delete("--checked");
}
}
// (other code)
}
The web page can now style the custom element’s internal states via custom pseudo-classes of the same name. In our example, the --checked
state is exposed via the :--checked
pseudo-class.
labeled-checkbox {
/* styles for the default state */
}
labeled-checkbox:--checked {
/* styles for the --checked state */
}
Browser vendors have been debating for the past three years how to expose the internal states of custom elements via custom pseudo-classes. Google’s Custom State Pseudo Class specification remains an “unofficial draft” hosted by WICG. The feature underwent a design review at the W3C TAG and has been handed over to the CSS Working Group. In Chrome’s ”intent to ship” discussion, Mounir Lamouri wrote this:
It looks like this feature has good support. It sounds that it may be hard for web developers to benefit from it as long as it’s not widely shipped, but hopefully Firefox and Safari will follow and implement it too. Someone has to implement it first, and given that there are no foreseeable backward incompatible changes, it sounds safe to go first.
We now have to wait for the implementations in Firefox and Safari. The browser bugs have been filed (Mozilla #1588763 and WebKit #215911) but have not received much attention yet.
Custom State Pseudo-Classes in Chrome originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>:focus-visible
and input placeholders, to accessible typefaces and a Safari bug with :display: contents
. Plus, a snippet for a bare-bones web component that supports …
Platform News: Using :focus-visible, BBC’s New Typeface, Declarative Shadow DOMs, A11Y and Placeholders originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>:focus-visible
and input placeholders, to accessible typefaces and a Safari bug with :display: contents
. Plus, a snippet for a bare-bones web component that supports style encapsulation.
The CSS :focus-visible
pseudo-class replaces :focus
as the new way to create custom focus indicators for keyboard users. Chrome recently switched from :focus
to :focus-visible
in the user agent stylesheet and, as a result of that change, the default focus ring is no longer shown when the user clicks or taps a button.
When switching from :focus
to :focus-visible
, consider backwards compatibility. Your keyboard focus indicators should be clearly visible in all browsers, not just the ones that support :focus-visible
. If you only style :focus-visible
, non-supporting browsers will show the default focus ring which, depending on your design, “may not be sufficiently clear or visible at all.”
button {
background: white;
}
button:focus-visible {
outline: none;
background: #ffdd00; /* gold */
}
A good way to start using :focus-visible
today is to define the focus styles in a :focus
rule and then immediately undo these same styles in a :focus:not(:focus-visible)
rule. This is admittedly not the most elegant and intuitive pattern, but it works well in all browsers:
:focus-visible
use the focus styles defined in the :focus
rule and ignore the second style rule completely (because :focus-visible
is unknown to them).:focus-visible
, the second style rule reverts the focus styles defined in the :focus
rule if the :focus-visible
state isn’t active as well. In other words, the focus styles defined in the :focus
rule are only in effect when :focus-visible
is also active.button:focus {
outline: none;
background: #ffdd00; /* gold */
}
button:focus:not(:focus-visible) {
background: white; /* undo gold */
}
The BBC created their own custom typeface called Reith (named after the BBC’s founder Sir John Reith). Their goal was to create a font that supports multiple languages and is easier to read, especially on small devices. The font was tested with mixed-ability user groups (dyslexia and vision impairment) and across different screen sizes.
We [the BBC] were using Helvetica or Arial. We also had Gill Sans as the corporate typeface. These typefaces were designed a hundred years ago for the printed page [and] don’t perform well on today’s modern digital screens.
Note: If you’d like to inspect Reith Sans and Reith Serif in Wakamai Fondue, you can quickly access the URLs of the WOFF2 files in the “All fonts on page” section of the Fonts pane in Firefox’s DOM inspector on BBC’s website.
display: contents
is still not accessible in SafariThe CSS display: contents
value has been supported in browsers since 2018. An element with this value “does not generate any boxes” and is effectively replaced by its children. This is especially useful in flex and grid layouts, where the contents
value can be used to “promote” more deeply nested elements to flex/grid items while retaining a semantic document structure.
Unfortunately, this feature originally shipped with an implementation bug that removed the element from the browser’s accessibility tree. For example, applying display: contents
to a <ul>
element resulted in that element no longer mentioned by screen readers. Since then, this bug has been fixed in Firefox and Chrome (in the latest version).
In Chrome and Firefox, the screen reader informs the user that the “Main, navigation” contains a “list, 2 items.” In Safari, the latter part is missing because the <ul>
and <li>
elements are not present in the accessibility tree. Until Apple fixes this bug in Safari, be careful when using the contents
value on semantic elements and test in screen readers to confirm that your pages are accessible in Safari as well.
opacity
when overriding the color of placeholder textAccessibility experts recommend avoiding placeholders if possible because they can be confused for pre-populated text and disappear when the user starts entering a value. However, many websites (including Wikipedia and GOV.UK) use placeholders in simple web forms that contain only a single input field, such as a search field.
Placeholders can be styled via the widely supported ::placeholder
pseudo-element. If your design calls for a custom color for placeholder text, make sure to specify both color
and opacity
. The latter is needed for Firefox, which applies opacity: 0.54
to ::placeholder
by default. If you don’t override this value, your placeholder text may have insufficient contrast in Firefox.
.search-field::placeholder {
color: #727272;
opacity: 1; /* needed for Firefox */
}
One of the key features of shadow DOM is style encapsulation, wherein the outer page’s style rules don’t match elements inside the shadow tree, and vice versa. In order to use this feature, you need to attach a shadow DOM tree to an element (usually a custom element, like <my-element>
) and copy the element’s template (usually from a <template>
element in the DOM) to the element’s newly created shadow DOM.
These steps can only be performed in JavaScript. If you’re only interested in style encapsulation and don’t need any dynamic functionality for your element, here is the minimum amount of JavaScript required to create a custom element with a shadow DOM:
customElements.define(
"my-element",
class extends HTMLElement {
constructor() {
super();
// find <template id="my-template"> in the DOM
let template = document.getElementById("my-template");
// make a copy of the template contents…
let content = template.content.cloneNode(true);
// …and inject it into <my-element>’s shadow DOM
this.attachShadow({ mode: "open" }).appendChild(content);
}
}
);
For an example of style encapsulation, see Miriam Suzanne’s <media-object>
element on CodePen. The scoped styles are located in the <template>
element in the HTML pane. Notice how this CSS code can use simple selectors, such as article
, that only match elements inside <media-object>
’s shadow DOM.
JavaScript may soon no longer be required to create this type of style encapsulation in modern browsers. Earlier this week, Chrome became the first browser to ship Google’s Declarative Shadow DOM proposal. If it becomes a standard, this feature will also make it possible to use Shadow DOM in combination with server-side rendering.
Platform News: Using :focus-visible, BBC’s New Typeface, Declarative Shadow DOMs, A11Y and Placeholders originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Platform News: Rounded Outlines, GPU-Accelerated SVG Animations, How CSS Variables Are Resolved originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Let’s jump in the news!
The idea to have the outline follow the border curve has existed ever since it became possible to create rounded borders via the border-radius
property in the mid 2000s. It was suggested to Mozilla, WebKit, and Chromium over ten years ago, and it’s even been part of the CSS UI specification since 2015:
The parts of the outline are not required to be rectangular. To the extent that the outline follows the border edge, it should follow the
border-radius
curve.
Fast-forward to today in 2021 and outlines are still rectangles in every browser without exception:
But this is finally starting to change. In a few weeks, Firefox will become the first browser with rounded outlines that automatically follow the border shape. This will also apply to Firefox’s default focus outline on buttons.
Please star Chromium Issue #81556 (sign in required) to help prioritize this bug and bring rounded outlines to Chrome sooner rather than later.
Until recently, animating an SVG element via CSS would trigger repaint on every frame (usually 60 times per second) in Chromium-based browsers. Such constant repainting can have a negative impact on the smoothness of the animation and the performance of the page itself.
The latest version of Chrome has eliminated this performance issue by enabling hardware acceleration for SVG animations. This means that SVG animations are offloaded to the GPU and no longer run on the main thread.
The switch to GPU acceleration automatically made SVG animations more performant in Chromium-based browsers (Firefox does this too), which is definitely good news for the web:
Hooray for more screen reader-accessible, progressively enhanced SVG animations and less Canvas.
CSS defines six physical units, including in
(inches) and cm
(centimeters). Every physical unit is in a fixed ratio with the pixel unit, which is the canonical unit. For example, 1in
is always exactly 96px
. On most modern screens, this length does not correspond to 1 real-world inch.
The FAQ page of the CSS Working Group now answers the question why there can’t be real physical units in CSS. In short, the browser cannot always determine the exact size and resolution of the display (think projectors). For websites that need accurate real-world units, the Working Group recommends per-device calibration:
Have a calibration page, where you ask the user to measure the distance between two lines that are some CSS distance apart (say,
10cm
), and input the value they get. Use this to find the scaling factor necessary for that screen (CSS length divided by user-provided length).
This scaling factor can then be set to a custom property and used to compute accurate lengths in CSS:
html {
--unit-scale: 1.428;
}
.box {
/* 5 real-world centimeters */
width: calc(5cm * var(--unit-scale, 1));
}
The NYT Open team wrote about some of the improvements to the New York Times website that have made it more accessible in recent years. The website uses semantic HTML (<article>
, <nav>
, etc.), increased contrast on important components (e.g., login and registration), and skip-to-content links that adapt to the site’s paywall.
Furthermore, the Games team made the daily crossword puzzle accessible to keyboard and screen reader users. The crossword is implemented as a grid of SVG <rect>
elements. As the user navigates through the puzzle, the current square’s aria-label
attribute (accessible name) is dynamically updated to provide additional context.
You can play the mini crossword without an account. Try solving the puzzle with the keyboard.
Yuan Chuan recently shared a little CSS quiz that I didn’t answer correctly because I wasn’t sure if a CSS variable (the var()
function) is resolved before or after the value is inherited. I’ll try to explain how this works on the following example:
html {
--text-color: var(--main-color, black);
}
footer {
--main-color: brown;
}
p {
color: var(--text-color);
}
The question: Is the color of the paragraph in the footer black
or brown
? There are two possibilities. Either (A) the declared values of both custom properties are inherited to the paragraph, and then the color
property resolves to brown
, or (B) the --text-color
property resolves to black
directly on the <html>
element, and then this value is inherited to the paragraph and assigned to the color
property.
The correct answer is option B (the color is black). CSS variables are resolved before the value is inherited. In this case, --text-color
falls back to black
because --main-color
does not exist on the <html>
element. This rule is specified in the CSS Variables module:
It is important to note that custom properties resolve any
var()
functions in their values at computed-value time, which occurs before the value is inherited.
Platform News: Rounded Outlines, GPU-Accelerated SVG Animations, How CSS Variables Are Resolved originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>prefers-contrast
lands in Safari, MathML gets some attention, :is()
is actually quite forgiving, more ADA-related lawsuits, inconsistent initial values for CSS Backgrounds properties can lead to unwanted — but sorta neat — patterns.
The prefers-contrast: more
…
Platform News: Prefers Contrast, MathML, :is(), and CSS Background Initial Values originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>prefers-contrast
lands in Safari, MathML gets some attention, :is()
is actually quite forgiving, more ADA-related lawsuits, inconsistent initial values for CSS Backgrounds properties can lead to unwanted — but sorta neat — patterns.
prefers-contrast: more
media query is supported in Safari PreviewAfter prefers-reduced-motion
in 2017, prefers-color-scheme
in 2019, and forced-colors
in 2020, a fourth user preference media feature is making its way to browsers. The CSS prefers-contrast: more
media query is now supported in the preview version of Safari. This feature will allow websites to honor a user’s preference for increased contrast.
.pricing-info {
color: #86868b; /* contrast ratio 3.5:1 */
}
@media (prefers-contrast: more) {
.pricing-info {
color: #535283; /* contrast ratio 7:1 */
}
}
One of the earliest specifications developed by the W3C in the mid-to-late ’90s was a markup language for displaying mathematical notations on the web called MathML. This language is currently supported in Firefox and Safari. Chrome’s implementation was removed in 2013 because of “concerns involving security, performance, and low usage on the Internet.”
If you’re using Chrome or Edge, enable “Experimental Web Platform features” on the about:flags
page to view the demo.
There is a renewed effort to properly integrate MathML into the web platform and bring it to all browsers in an interoperable way. Igalia has been developing a MathML implementation for Chromium since 2019. The new MathML Core Level 1 specification is a fundamental subset of MathML 3 (2014) that is “most suited for browser implementation.” If approved by the W3C, a new Math Working Group will work on improving the accessibility and searchability of MathML.
The mission of the Math Working Group is to promote the inclusion of mathematics on the Web so that it is a first-class citizen of the web that displays well, is accessible, and is searchable.
:is()
upgrades selector lists to become forgivingThe new CSS :is()
and :where()
pseudo-classes are now supported in Chrome, Safari, and Firefox. In addition to their standard use cases (reducing repetition and keeping specificity low), these pseudo-classes can also be used to make selector lists “forgiving.”
For legacy reasons, the general behavior of a selector list is that if any selector in the list fails to parse […] the entire selector list becomes invalid. This can make it hard to write CSS that uses new selectors and still works correctly in older user agents.
In other words, “if any part of a selector is invalid, it invalidates the whole selector.” However, wrapping the selector list in :is()
makes it forgiving: Unsupported selectors are simply ignored, but the remaining selectors will still match.
Unfortunately, pseudo-elements do not work inside :is()
(although that may change in the future), so it is currently not possible to turn two vendor-prefixed pseudo-elements into a forgiving selector list to avoid repeating styles.
/* One unsupported selector invalidates the entire list */
::-webkit-slider-runnable-track, ::-moz-range-track {
background: red;
}
/* Pseudo-elements do not work inside :is() */
:is(::-webkit-slider-runnable-track, ::-moz-range-track) {
background: red;
}
/* Thus, the styles must unfortunately be repeated */
::-webkit-slider-runnable-track {
background: red;
}
::-moz-range-track {
background: red;
}
More and more American businesses are facing lawsuits over accessibility issues on their websites. Most recently, the tech corporation Dell was sued by a visually impaired person who was unable to navigate Dell’s website and online store using the JAWS and VoiceOver screen readers.
The Defendant fails to communicate information about its products and services effectively because screen reader auxiliary aids cannot access important content on the Digital Platform. […] The Digital Platform uses visual cues to convey content and other information. Unfortunately, screen readers cannot interpret these cues and communicate the information they represent to individuals with visual disabilities.
Earlier this year, Kraft Heinz Foods Company was sued for failing to comply with the Web Content Accessibility Guidelines on one of the company’s websites. The complaint alleges that the website did not declare a language (lang
attribute) and provide accessible labels for its image links, among other things.
In the United States, the Americans with Disabilities Act (ADA) applies to websites, which means that people can sue retailers if their websites are not accessible. According to the CEO of Deque Systems (the makers of axe), the recent increasing trend of web-based ADA lawsuits can be attributed to a lack of a single overarching regulation that would provide specific compliance requirements.
background-clip
and background-origin
have different initial valuesBy default, a CSS background is painted within the element’s border box (background-clip: border-box
) but positioned relative to the element’s padding box (background-origin: padding-box
). This inconsistency can result in unexpected patterns if the element’s border is semi-transparent or dotted/dashed.
.box {
/* semi-transparent border */
border: 20px solid rgba(255, 255, 255, 0.25);
/* background gradient */
background: conic-gradient(
from 45deg at bottom left,
deeppink,
rebeccapurple
);
}
Because of the different initial values, the background gradient in the above image is repeated as a tiled image on all sides under the semi-transparent border. In this case, positioning the background relative to the border box (background-origin: border-box
) makes more sense.
Platform News: Prefers Contrast, MathML, :is(), and CSS Background Initial Values originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Platform News: Defaulting to Logical CSS, Fugu APIs, Custom Media Queries, and WordPress vs. Italics originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Let’s jump right into the news…
Six years after Mozilla shipped the first bits of CSS Logical Properties in Firefox, this feature is now on a path to full browser support in 2021. The categories of logical properties and values listed in the table below are already supported in Firefox, Chrome, and the latest Safari Preview.
CSS property or value | The logical equivalent |
---|---|
margin-top | margin-block-start |
text-align: right | text-align: end |
bottom | inset-block-end |
border-left | border-inline-start |
(n/a) | margin-inline |
Logical CSS also introduces a few useful shorthands for tasks that in the past required multiple declarations. For example, margin-inline
sets the margin-left
and margin-right
properties, while inset
sets the top
, right
, bottom
and left
properties.
/* BEFORE */
main {
margin-left: auto;
margin-right: auto;
}
/* AFTER */
main {
margin-inline: auto;
}
A website can add support for an RTL (right-to-left) layout by replacing all instances of left
and right
with their logical counterparts in the site’s CSS code. Switching to logical CSS makes sense for all websites because the user may translate the site to a language that is written right-to-left using a machine translation service. The biggest languages with RTL scripts are Arabic (310 million native speakers), Persian (70 million), and Urdu (70 million).
/* Switch to RTL when Google translates the page to an RTL language */
.translated-rtl {
direction: rtl;
}
David Bushell’s personal website now uses logical CSS and relies on Google’s translated-rtl
class to toggle the site’s inline base direction. Try translating David’s website to an RTL language in Chrome and compare the RTL layout with the site’s default LTR layout.
Last week Chrome shipped three web APIs for “advanced hardware interactions”: the WebHID and Web Serial APIs on desktop, and Web NFC on Android. All three APIs are part of Google’s capabilities project, also known as Project Fugu, and were developed in W3C community groups (though they’re not web standards).
Apple and Mozilla, the developers of the other two major browser engines, are currently opposed to these APIs. Apple has decided to “not yet implement due to fingerprinting, security, and other concerns.” Mozilla’s concerns are summarized on the Mozilla Specification Positions page.
preserveAspectRatio=none
By default, an SVG scales to fit the <svg>
element’s content box, while maintaining the aspect ratio defined by the viewBox
attribute. In some cases, the author may want to stretch the SVG so that it completely fills the content box on both axes. This can be achieved by setting the preserveAspectRatio
attribute to none
on the <svg>
element.
Distorting SVG in this manner may seem counterintuitive, but disabling aspect ratio via the preserveAspectRatio=none
value can make sense for simple, decorative SVG graphics on a responsive web page:
This value can be useful when you are using a path for a border or to add a little effect on a section (like a diagonal [line]), and you want the path to fill the space.
An italic font can be used to highlight important words (e.g., the <em>
element), titles of creative works (<cite>
), technical terms, foreign phrases (<i>
), and more. Italics are helpful when used discreetly in this manner, but long sections of italic text are considered an accessibility issue and should be avoided.
Italicized text can be difficult to read for some people with dyslexia or related forms of reading disorders.
WordPress 5.7, which was released earlier this week, removed italics on descriptions, help text, labels, error details text, and other places in the WordPress admin to “improve accessibility and readability.”
In related news, WordPress 5.7 also dropped custom web fonts, opting for system fonts instead.
The CSS Media Queries Level 5 module specifies a @custom-media
rule for defining custom media queries. This proposed feature was originally added to the CSS spec almost seven years ago (in June 2014) and has since then not been further developed nor received any interest from browser vendors.
@custom-media --narrow-window (max-width: 30em);
@media (--narrow-window) {
/* narrow window styles */
}
A media query used in multiple places can instead be assigned to a custom media query, which can be used everywhere, and editing the media query requires touching only one line of code.
Custom media queries may not ship in browsers for quite some time, but websites can start using this feature today via the official PostCSS plugin (or PostCSS Preset Env) to reduce code repetition and make media queries more readable.
On a related note, there is also the idea of author-defined environment variables, which (unlike custom properties) could be used in media queries, but this potential feature has not yet been fully fleshed out in the CSS spec.
@media (max-width: env(--narrow-window)) {
/* narrow window styles */
}
Platform News: Defaulting to Logical CSS, Fugu APIs, Custom Media Queries, and WordPress vs. Italics originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Chrome will…
Weekly Platform News: Focus Rings, Donut Scope, More em Units, and Global Privacy Control originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Chrome, Edge, and other Chromium-based browsers display a focus indicator (a.k.a. focus ring) when the user clicks or taps a (styled) button. For comparison, Safari and Firefox don’t display a focus indicator when a button is clicked or tapped, but do only when the button is focused via the keyboard.
Some developers find this behavior annoying and are using various workarounds to prevent the focus ring from appearing when a button is clicked or tapped. For example, the popular what-input library continuously tracks the user’s input method (mouse, keyboard or touch), allowing the page to suppress focus rings specifically for mouse clicks.
[data-whatintent="mouse"] :focus {
outline: none;
}
A more recent workaround was enabled by the addition of the CSS :focus-visible
pseudo-class to Chromium a few months ago. In the current version of Chrome, clicking or tapping a button invokes the button’s :focus
state but not its :focus-visible
state. that way, the page can use a suitable selector to suppress focus rings for clicks and taps without affecting keyboard users.
:focus:not(:focus-visible) {
outline: none;
}
Fortunately, these workarounds will soon become unnecessary. Chromium’s user agent stylesheet recently switched from :focus
to :focus-visible
, and as a result of this change, button clicks and taps no longer invoke focus rings. The new behavior will first ship in Chrome 90 next month.
:not()
selector enables “donut scope”I recently wrote about the A:not(B *)
selector pattern that allows authors to select all A
elements that are not descendants of a B
element. This pattern can be expanded to A B:not(C *)
to create a “donut scope.”
For example, the selector article p:not(blockquote *)
matches all <p>
elements that are descendants of an <article>
element but not descendants of a <blockquote>
element. In other words, it selects all paragraphs in an article except the ones that are in a block quotation.
Announced last October, Global Privacy Control (GPC) is a new privacy signal for the web that is designed to be legally enforceable. Essentially, it’s an HTTP Sec-GPC: 1
request header that tells websites that the user does not want their personal data to be shared or sold.
The New York Times has become the first major publisher to honor GPC. A number of other publishers, including The Washington Post and Automattic (WordPress.com), have committed to honoring it “this coming quarter.”
From NYT’s privacy page:
Does The Times support the Global Privacy Control (GPC)?
Yes. When we detect a GPC signal from a reader’s browser where GDPR, CCPA or a similar privacy law applies, we stop sharing the reader’s personal data online with other companies (except with our service providers).
em
-based media queriesSome browsers allow the user to increase the default font size in the browser’s settings. Unfortunately, this user preference has no effect on websites that set their font sizes in pixels (e.g., font-size: 20px
). In part for this reason, some websites (including CSS-Tricks) instead use font-relative units, such as em
and rem
, which do respond to the user’s font size preference.
Ideally, a website that uses font-relative units for font-size
should also use em
values in media queries (e.g., min-width: 80em
instead of min-width: 1280px
). Otherwise, the site’s responsive layout may not always work as expected.
For example, CSS-Tricks switches from a two-column to a one-column layout on narrow viewports to prevent the article’s lines from becoming too short. However, if the user increases the default font size in the browser to 24px
, the text on the page will become larger (as it should) but the page layout will not change, resulting in extremely short lines at certain viewport widths.
If you’d like to try out em
-based media queries on your website, there is a PostCSS plugin that automatically converts min-width
, max-width
, min-height
, and max-height
media queries from px
to em
.
:user-invalid
to browsersIn 2017, Peter-Paul Koch published a series of three articles about native form validation on the web. Part 1 points out the problems with the widely supported CSS :invalid
pseudo-class:
<input>
elements is re-evaluated on every key stroke, so a form field can become :invalid
while the user is still typing the value.<input required>
), it will become :invalid
immediately on page load.Both of these behaviors are potentially confusing (and annoying), so websites cannot rely solely on the :invalid
selector to indicate that a value entered by the user is not valid. However, there is the option to combine :invalid
with :not(:focus)
and even :not(:placeholder-shown)
to ensure that the page’s “invalid” styles do not apply to the <input>
until the user has finished entering the value and moved focus to another element.
The CSS Selectors module defines a :user-invalid
pseudo-class that avoids the problems of :invalid
by only matching an <input>
“after the user has significantly interacted with it.”
Firefox already supports this functionality via the :-moz-ui-invalid
pseudo-class (see it in action). Mozilla now intends to un-prefix this pseudo-class and ship it under the standard :user-invalid
name. There are still no signals from other browser vendors, but the Chromium and WebKit bugs for this feature have been filed.
Weekly Platform News: Focus Rings, Donut Scope, More em Units, and Global Privacy Control originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]><popup>
element, check the use of prefers-reduced-motion
on award-winning sites, learn how to opt into cross-origin isolation, see how WhiteHouse.gov approaches accessibility, and warn the dangers of 100vh
.…
Weekly Platform News: Reduced Motion, CORS, WhiteHouse.gov, popups, and 100vw originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]><popup>
element, check the use of prefers-reduced-motion
on award-winning sites, learn how to opt into cross-origin isolation, see how WhiteHouse.gov approaches accessibility, and warn the dangers of 100vh
.
Let’s get into the news!
<popup>
element is in developmentOn January 21, Melanie Richards from the Microsoft Edge web platform team posted an explainer for a proposed HTML <popup>
element (the name is not final). A few hours later, Mason Freed from Google announced an intent to prototype this element in the Blink browser engine. Work on the implementation is taking place in Chromium issue #1168738.
A popup is a temporary (transient) and “light-dismissable” UI element that is displayed on the the “top layer” of all other elements. The goal for the <popup>
element is to be fully stylable and accessible by default. A <popup>
can be anchored to an activating element, such as a button, but it can also be a standalone element that is displayed on page load (e.g., a teaching UI).
A <popup>
is automatically hidden when the user presses the Esc key or moves focus to a different element (this is called a light dismissal). Unlike the <dialog>
element, only one <popup>
can be shown at a time, and unlike the deprecated <menu>
element, a <popup>
can contain arbitrary content, including interactive elements:
We imagine
<popup>
as being useful for various different types of popover UI. We’ve chosen to use an action menu as a primary example, but folks use popup-esque patterns for many different types of content.
Earlier this week, AWWWARDS announced the winners of their annual awards for the best websites of 2020. The following websites were awarded:
All these websites are highly dynamic and show full-screen motion on load and during scroll. Unfortunately, such animations can cause dizziness and nausea in people with vestibular disorders. Websites are therefore advised to reduce or turn off non-essential animations via the prefers-reduced-motion
media query, which evaluates to true for people who have expressed their preference for reduced motion (e.g., the “Reduce motion” option on Apple’s platforms). None of the winning websites do this.
/* (code from animal-crossing.com) */
@media (prefers-reduced-motion: reduce) {
.main-header__scene {
animation: none;
}
}
An example of a website that does this correctly is the official site of last year’s Animal Crossing game. Not only does the website honor the user’s preference via prefers-reduced-motion
, but it also provides its own “Reduce motion” toggle button at the very top of the page.
Cross-origin isolation is part of a “long-term security improvement.” Websites can opt into cross-origin isolation by adding the following two HTTP response headers, which are already supported in Chrome and Firefox:
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
A cross-origin-isolated page relinquishes its ability to load content from other origins without their explicit opt-in (via CORS headers), and in return, the page gains access to some powerful APIs, such as SharedArrayBuffer
.
if (crossOriginIsolated) {
// post SharedArrayBuffer
} else {
// do something else
}
The new WhiteHouse.gov website of the Biden administration was built from scratch in just six weeks with accessibility as a top priority (“accessibility was top of mind”). Microsoft’s chief accessibility officer reviewed the site and gave it the thumbs up.
The website’s accessibility statement (linked from the site’s footer) declares a “commitment to accessibility” and directly references the latest version of the Web Content Accessibility Guidelines, WCAG 2.1 (2018). This is notable because federal agencies in the USA are only required to comply with WCAG 2.0 (2008).
The Web Content Accessibility Guidelines are the most widely accepted standards for internet accessibility. … By referencing WCAG 2.1 (the latest version of the guidelines), the Biden administration may be indicating a broader acceptance of the WCAG model.
100vw
value can cause a useless horizontal scrollbarOn Windows, when a web page has a vertical scrollbar, that scrollbar consumes space and reduces the width of the page’s <html>
element; this is called a classic scrollbar. The same is not the case on macOS, which uses overlay scrollbars instead of classic scrollbars; a vertical overlay scrollbar does not affect the width of the <html>
element.
macOS users can switch from overlay scrollbars to classic scrollbars by setting “Show scroll bars” to ”Always” in System preferences > General.
The CSS length value 100vw
is equal to the width of the <html>
element. However, if a classic vertical scrollbar is added to the page, the <html>
element becomes narrower (as explained above), but 100vw
stays the same. In other words, 100vw
is wider than the page when a classic vertical scrollbar is present.
This can be a problem for web developers on macOS who use 100vw
but are unaware of its peculiarity. For example, a website might set width: 100vw
on its article header to make it full-width, but this will cause a useless horizontal scrollbar on Windows that some of the site’s visitors may find annoying.
Web developers on macOS can switch to classic scrollbars to make sure that overflow bugs caused by 100vw
don’t slip under their radar. In the meantime, I have asked the CSS Working Group for comment.
Weekly Platform News: Reduced Motion, CORS, WhiteHouse.gov, popups, and 100vw originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Weekly Platform News: WebKit autofill, Using Cursor Pointer, Delaying Autoplay Videos originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>::-webkit-autofill
has become a standard featureChrome, Safari, and pretty much every other modern web browser except Firefox (more on that later) have supported the CSS :-webkit-autofill
pseudo-class for many years. This selector matches form fields that have been autofilled by the browser. Websites can use this feature to style autofilled fields in CSS (with some limitations) and detect such fields in JavaScript.
let autofilled = document.querySelectorAll(":-webkit-autofill");
There currently does not exist a standard autocomplete
or autofill
event that would fire when the browser autofills a form field, but you can listen to the input
event on the web form and then check if any of its fields match the :-webkit-autofill
selector.
The HTML Standard has now standardized this feature by adding :autofill
(and :-webkit-autofill
as an alias) to the list of pseudo-classes that match HTML elements. This pseudo-class will also be added to the CSS Selectors module.
The
:autofill
and:-webkit-autofill
pseudo-classes must match<input>
elements that have been autofilled by the user agent. These pseudo-classes must stop matching if the user edits the autofilled field.
Following standardization, both pseudo-classes have been implemented in Firefox and are expected to ship in Firefox 86 later this month.
In the article “Let’s Bring Spacer GIFs Back!” Josh W. Comeau argues for using a “spacer” <span>
element instead of a simple CSS margin to define the spacing between the icon and text of a button component.
In our home-button example, should the margin go on the back-arrow, or the text? It doesn’t feel to me like either element should “own” the space. It’s a distinct layout concern.
CSS Grid is an alternative to such spacer elements. For example, the “Link to issue” link in CSS-Tricks’s newsletter section contains two non-breaking spaces (
) to increase the spacing between the emoji character and text, but the link could instead be turned into a simple grid layout to gain finer control over the spacing via the gap
property.
The CSS Basic User Interface module defines the CSS cursor
property, which allows websites to change the type of cursor that is displayed when the user hovers specific elements. The specification has the following to say about the property’s pointer
value:
The cursor is a pointer that indicates a link. … User agents must apply
cursor: pointer
to hyperlinks. … Authors should usepointer
on links and may use on other interactive elements.
Accordingly, browsers display the pointer
cursor (rendered as a hand) on links and the default
cursor (rendered as an arrow) on buttons. However, most websites (including Wikipedia) don’t agree with this default style and apply cursor: pointer
to other interactive elements, such as buttons and checkboxes, as well.
Another interactive element for which it makes sense to use the pointer
cursor is the <summary>
element (the “toggle button” for opening and closing the parent <details>
element).
autoplay
until the video comes into viewCompared to modern video formats, animated GIF images are up to “twice as expensive in energy use.” For that reason, browsers have relaxed their video autoplay policies (some time ago) to encourage websites to switch from GIFs to silent or muted videos.
<!-- a basic re-implementation of a GIF using <video> -->
<video autoplay loop muted playsinline src="meme.mp4"></video>
If you’re using <video muted autoplay>
, don’t worry about pausing such videos when they’re no longer visible in the viewport (e.g., using an Intersection Observer). All major browsers (except Firefox) already perform this optimization by default:
<video autoplay>
elements will only begin playing when visible on-screen such as when they are scrolled into the viewport, made visible through CSS, and inserted into the DOM.
@font-face
descriptorsDifferent browsers and operating systems sometimes use different font metrics even when rendering the same font. These differences affect the vertical position of text, which is especially noticeable on large headings.
Similarly, the different font metrics of a web font and its fallback font can cause a layout shift when the fonts are swapped during page load.
To help websites avoid layout shift and create interoperable text layouts, Chrome recently added the following three new CSS @font-face
descriptors for overriding the font’s default metrics:
ascent-override
(ascent is the height above the baseline)descent-override
(descent is the depth below the baseline)line-gap-override
@font-face {
font-family: Roboto;
/* Merriweather Sans has 125.875px ascent
* and 35px descent at 128px font size.
*/
ascent-override: calc(125.875 / 128 * 100%);
descent-override: calc(35 / 128 * 100%);
src: local(Roboto-Regular);
}
The following video shows how overriding the ascent and descent metrics of the fallback font (Roboto) to match the same metrics of the web font (Merriweather Sans) can avoid layout shift when swapping between these two fonts.
Weekly Platform News: WebKit autofill, Using Cursor Pointer, Delaying Autoplay Videos originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>In this week’s update, the CSS :not
pseudo class can accept complex selectors, how to disable smooth scrolling when using “Find on page…” in Chrome, Safari’s …
Weekly Platform News: The :not() pseudo-class, Video Media Queries, clip-path: path() Support originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>In this week’s update, the CSS :not
pseudo class can accept complex selectors, how to disable smooth scrolling when using “Find on page…” in Chrome, Safari’s support for there media
attribute on <video>
elements, and the long-awaited debut of the path()
function for the CSS clip-path
property.
Let’s jump into the news…
After a years-long wait, the enhanced :not()
pseudo-class has finally shipped in Chrome and Firefox, and is now supported in all major browser engines. This new version of :not()
accepts complex selectors and even entire selector lists.
For example, you can now select all <p>
elements that are not contained within an <article>
element.
/* select all <p>s that are descendants of <article> */
article p {
}
/* NEW! */
/* select all <p>s that are not descendants of <article> */
p:not(article *) {
}
In another example, you may want to select the first list item that does not have the hidden
attribute (or any other attribute, for that matter). The best selector for this task would be :nth-child(1 of :not([hidden]))
, but the of
notation is still only supported in Safari. Luckily, this unsupported selector can now be re-written using only the enhanced :not()
pseudo-class.
/* select all non-hidden elements that are not preceded by a non-hidden sibling (i.e., select the first non-hidden child */
:not([hidden]):not(:not([hidden]) ~ :not([hidden])) {
}
Refresh
header can be an accessibility issueThe HTTP Refresh
header (and equivalent HTML <meta>
tag) is a very old and widely supported non-standard feature that instructs the browser to automatically and periodically reload the page after a given amount of time.
<!-- refresh page after 60 seconds -->
<meta http-equiv="refresh" content="60">
According to Google’s data, the <meta http-equiv="refresh">
tag is used by a whopping 2.8% of page loads in Chrome (down from 4% a year ago). All these websites risk failing several success criteria of the Web Content Accessibility Guidelines (WCAG):
If the time interval is too short, and there is no way to turn auto-refresh off, people who are blind will not have enough time to make their screen readers read the page before the page refreshes unexpectedly and causes the screen reader to begin reading at the top.
However, WCAG does allow using the <meta http-equiv="refresh">
tag specifically with the value 0
to perform a client-side redirect in the case that the author does not control the server and hence cannot perform a proper HTTP redirect.
CSS scroll-behavior: smooth
is supported in Chrome and Firefox. When this declaration is set on the <html>
element, the browser scrolls the page “in a smooth fashion.” This applies to navigations, the standard scrolling APIs (e.g., window.scrollTo({ top: 0 })
), and scroll snapping operations (CSS Scroll Snap).
Unfortunately, Chrome erroneously keeps smooth scrolling enabled even when the user performs a text search on the page (“Find on page…” feature). Some people find this annoying. Until that is fixed, you can use Christian Schaefer’s clever CSS workaround that effectively disables smooth scrolling for the “Find on page…” feature only.
@keyframes smoothscroll1 {
from,
to {
scroll-behavior: smooth;
}
}
@keyframes smoothscroll2 {
from,
to {
scroll-behavior: smooth;
}
}
html {
animation: smoothscroll1 1s;
}
html:focus-within {
animation-name: smoothscroll2;
scroll-behavior: smooth;
}
In the following demo, notice how clicking the links scrolls the page smoothly while searching for the words “top” and “bottom” scrolls the page instantly.
media
attribute on video sourcesWith the HTML <video>
element, it is possible to declare multiple video sources of different MIME types and encodings. This allows websites to use more modern and efficient video formats in supporting browsers, while providing a fallback for other browsers.
<video>
<source src="/flower.webm" type="video/webm">
<source src="/flower.mp4" type="video/mp4">
</video>
In the past, browsers also supported the media
attribute on video sources. For example, a web page could load a higher-resolution video if the user’s viewport exceeded a certain size.
<video>
<source media="(min-width: 1200px)" src="/large.mp4" type="video/mp4">
<source src="/small.mp4" type="video/mp4">
</video>
The above syntax is in fact still supported in Safari today, but it was removed from other browsers around 2014 because it was not considered a good feature:
It is not appropriate for choosing between low resolution and high resolution because the environment can change (e.g., the user might fullscreen the video after it has begun loading and want high resolution). Also, bandwidth is not available in media queries, but even if it was, the user agent is in a better position to determine what is appropriate than the author.
Scott Jehl (Filament Group) argues that the removal of this feature was a mistake and that websites should be able to deliver responsive video sources using <video>
alone.
For every video we embed in HTML, we’re stuck with the choice of serving source files that are potentially too large or small for many users’ devices … or resorting to more complicated server-side or scripted or third-party solutions to deliver a correct size.
Scott has written a proposal for the reintroduction of media
in video <source>
elements and is welcoming feedback.
clip-path: path()
function ships in ChromeIt wasn’t mentioned in the latest “New in Chrome 88” article, but Chrome just shipped the path()
function for the CSS clip-path
property, which means that this feature is now supported in all three major browser engines (Safari, Firefox, and Chrome).
The path()
function is defined in the CSS Shapes module, and it accepts an SVG path string. Chris calls this the ultimate syntax for the clip-path
property because it can clip an element with “literally any shape.” For example, here’s a photo clipped with a heart shape:
Weekly Platform News: The :not() pseudo-class, Video Media Queries, clip-path: path() Support originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Let’s get into the news.
Firefox for Android will block tracking content
Mozilla has announced that …
Weekly Platform News: Strict Tracking Protection, Dark Web Pages, Periodic Background Sync originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Let’s get into the news.
Mozilla has announced that the upcoming revamped Firefox for Android (currently available in a test version under the name “Firefox Preview”) will include strict tracking protection by default.
On the phone or tablet, most users care much more about performance and blocking of annoyances compared to desktop. Users are more forgiving when a site doesn’t load exactly like it’s meant to. So we decided that while Firefox for desktop’s default mode is “Standard,” Firefox Preview will use “Strict” mode.
Strict tracking protection additionally blocks “tracking content”: ads, videos, and other content with tracking code.
(via Mozilla)
Opera for Android has added a “Dark web pages” option that renders all websites in dark mode. If a website does not provide dark mode styles (via the CSS prefers-color-scheme
media feature), Opera applies its own “clever CSS changes” to render the site in dark mode regardless.
(via Stefan Stjernelund)
Google intends to ship Periodic Background Sync in the next version of Chrome (early next year). This feature will enable installed web apps to run background tasks at periodic intervals with network connectivity.
Chrome’s implementation restricts the API to installed web apps. Chrome grants the permission on behalf of the user for any installed web app. The API is not available outside of installed PWAs.
Apple and Mozilla are currently opposed to this API. At Mozilla, there are opinions that the feature is “harmful in its current state,” while Apple states multiple privacy and security risks.
(via Mugdha Lakhani)
Read more news in my weekly newsletter for web developers. Pledge as little as $2 per month to get the latest news from me via email every Monday.
Weekly Platform News: Strict Tracking Protection, Dark Web Pages, Periodic Background Sync originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>