When Microsoft killed off Internet Explorer in June of 2022, front-end developers breathed a sigh of relief. IE's death meant that the development of lots of exciting features in CSS could now be fully supported across all major browsers (Chrome, Firefox, Safari, Edge) without worrying about IE compatibility.
So on this joyous occasion, we collected the thoughts of Lullabot's front-end developers and compiled some of the great new-ish features in CSS that we are thankful for. We also had an airing of grievances about some of the (many) things the CSS spec still needs.
CSS features for which we are thankful
Individual transform properties
Most front-end developers are familiar with the transform
property - it has lots of use cases for minor little tweaks (like a diagonal ribbon of text) or things like scaling an image on hover. Until recently, though, it could get a bit cumbersome to use because multiple transformations had to be included in one line.
.optimus-prime {
inline-size: 250px;
padding: 2em;
border-size: 3px;
border-style: solid;
background-color: orange;
border-color: blue;
transform: rotate(-5deg) translateX(10%) scale(1.2);
}
If you wanted to change just one transformation (for instance, the rotate
property on a :hover
state), you had to include all of the other properties as well.
.optimus-prime:hover {
transform: rotate(5deg) translateX(10%) scale(1.5);
}
However, as of Chrome 104 (released August 2022), these three properties - translate
, rotate
, and scale
can now be listed as individual properties, meaning less code and an easier life.
.optimus-prime:hover {
rotate: -5deg;
translate: 10% 0;
scale: 1.5;
}
Chrome was the last to adopt this, so now it can be widely used across all browsers. Here is a CodePen to demonstrate:
You can learn more from the Chrome team's blog post, but this is a welcome change for anyone working with animations.
:is() and :where() pseudo selectors
Have you ever had to apply the same styles to multiple HTML elements? Has it made your code long, convoluted, and hard to read? Well, no longer, now that :is()
and :where()
are supported in all major browsers!
Say you want to set the color of all heading elements within an <article>
.
article h1,
article h2,
article h3 {
color: rebeccapurple;
}
With :is()
and :where()
, you can now write the same code this way:
article :where(h1, h2, h3) {
color: rebeccapurple;
}
What's the difference between :is()
and :where()
? :where()
has no specificity. :is()
takes on the specificity of its most specific argument. In the next example article h1
will be #ccc
.
article h1 {
color: #ccc;
}
article :where(h1, h2, h3) {
color: #000;
}
But in this example article h1
will be #000
.
article h1 {
color: #ccc;
}
article :is(h1, h2, h3) {
color: #000;
}
MDN dives deep on :is()
and compares it to :where()
here if you need more clarification.
:not() pseudo selector
Supported in all major browsers, :not()
adds the ability to select elements that do not match a particular selector and takes on the specificity of its most specific selector. The :not()
selector operates in the opposite way of :is()
, which selects all elements of a particular selector.
With :not()
, it's possible to add a margin to the bottom of every list item except for the last. In the past, a developer would write:
li {
margin-bottom: 10px;
}
li:last-child {
margin-bottom: 0;
}
Now, this same block of code can be written this way, thus shortening the amount of code written:
li:not(:last-child) {
margin-bottom: 10px;
}
:not()
has been a welcome addition to the CSS landscape. Learn more about :not()
over at MDN.
CSS logical properties
While not exactly "new" (fully supported since 2017), CSS logical properties have started to become the de-facto choice for front-end developers looking to make their code flexible for all languages and layouts.
CSS logical properties are a CSS module that can manipulate a page layout through logical dimension and direction rather than physical dimension and direction. That means we use margin-inline-start
rather than margin-left
when setting the start of a margin.
CSS logical properties are centered around the block and inline axes that come from a page's writing direction. Inline is the direction characters naturally flow and block is the direction that text wraps. These properties allow developers to layout a page relative to the content it displays rather than the strict definition of left, right, top, and bottom.
For instance, say you are building a multi-lingual site and want to have a page of content and a sidebar with useful links. With languages that read left-to-right (LTR), you would likely want the sidebar to be on the right-hand side and the content on the left-hand side of the page, as that is how LTR content should be read. You could accomplish this using non-logical properties:
.content {
margin-right: 20px;
margin-bottom: 30px;
padding: 15px 20px;
}
.sidebar {
border-left: 1px solid #000;
padding-left: 10px;
}
But how would you approach this for languages that read right-to-left (RTL)? You would have to duplicate your code, change the direction for the padding and margin properties, and possibly add additional classes or some other control further up the style sheet:
.content-rtl {
margin-left: 20px;
margin-bottom: 30px;
padding: 15px 20px;
}
.sidebar-rtl {
border-right: 1px solid #000;
padding-right: 10px;
}
However, if you use CSS logical properties, you can limit your code to just two selectors and handle both layouts simultaneously.
.content {
margin-inline-end: 20px;
margin-block-end: 30px;
padding-block: 15px;
padding-inline: 20px;
}
.sidebar {
border-inline-start: 1px solid #000;
padding-inline-start: 10px;
}
The user's preferred language is built into browsers, so even before your CSS is rendered, the browser knows in which direction the user prefers the text to be read. By utilizing border-inline-start
for instance, it puts the border at the start of the horizontal area of the content where the .sidebar
class should be, regardless of the direction, so that for visitors preferring LTR text, it is on the left-hand side of the sidebar, but for RTL visitors, it would be on the right because that is where the sidebar appears to them.
We strive to make sites accessible for as broad an audience as possible, and CSS logical properties help us do that. We even published an architectural design record (ADR) about logical properties to help accomplish this. You can find a great reference guide about CSS logical properties over at MDN if you want to implement it in your projects.
CSS line clamp
-webkit-line-clamp
is a sneaky little trick that makes displaying teaser text a much more manageable task.
Originally part of Chrome way back in 2011, this "one little trick jQuery plugin authors can't stand" will let you create a four-line, ellipse-ending paragraph that works in all modern browsers:
.my-text-block {
display: -webkit-box;
text-overflow: ellipsis;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
}
Yep, you read that right. Even Firefox will accept and render the -webkit
prefixes on all of these properties. Though not officially part of the CSS spec, there was such widespread support for this feature that by 2019, it worked everywhere (except IE, of course). A push is underway to add a line-clamp property to CSS, but there will still be backward support for -webkit-line-clamp
included if this happens. So go ahead and clamp those lines! More information can be found at MDN.
CSS features we still need
Subgrid
CSS grid has been part of a web developer's toolkit since 2017, but what would make grid even more powerful? The ability to align elements within a grid item to the grid itself. Currently, this is not possible — without subgrid, developers resort to adding margins, padding, and other methods to align items just so.
Subgrid will ease the pain of many web developers who have had to create a grid within a grid. With subgrid, everything within a grid element aligns perfectly by setting grid-template-rows: subgrid
on whatever grid item you want to use the same grid as its parent. (This works for grid-template-columns
as well.)
You can see subgrid in action if you view the following Codepen in Safari 16 or Firefox. Card 2's content is longer than Card 1 and Card 3, and ideally, we'd want the learn more link to align on the same grid line on all three cards. In a browser that supports subgrid, you can see that enabling subgrid allows the contents inside the card to be aligned to the same grid as the cards themselves.
As of September 2022, subgrid is supported in Firefox and the newest version of Safari.
Container queries
Media queries for responsive design opened many doors for new and creative website layouts. Still, developers have also wanted more fine-grained control over when the size of an element should change. Enter container queries, which will allow the developer to add breakpoints at the container level.
To use container queries, first, define your container.
.my-fantastic-container {
container-type: inline-size;
container-name: main-container;
}
Now, say you want to resize a card within the container at a certain breakpoint. To do this, add the breakpoint this way:
@container main-container (min-width: 768px) {
.card {
width: 50%;
}
}
The card will now display at 50% width when .my-fantastic-container
is 768px or larger.
Container queries will be supported in the upcoming Safari 16 release and in Chrome 106, which will be released later in 2022.
:has() pseudo selector improvements
There has been a lot of progress on :has()
as of mid-2022, with Safari and Chrome (behind a flag) supporting it. But Firefox, Edge, and Chrome (without a flag) don't support it.
If unfamiliar, the :has()
property allows you to target something that…well, has a certain other element.
For instance, you can target all <p>
elements that have an img
within them like this:
p:has(img)
Which is incredibly useful! However, the :has()
attribute is currently lacking the ability to apply specificity as well.
Say you wanted to make any unordered list with at least ten items, display it in 2 columns. Code like this would be great if it worked:
ul:has(li:nth-child(10)) {
column-count: 2;
}
This same layout should be doable with :has()
but limitations on it in browsers prevent it from doing so. Here's a CodePen to demonstrate that will, for now, only work in Safari and Chrome.
In Safari and Chrome, you get three neat columns of US states, but it's just a long list everywhere else. That's because the specificity of the nth-child
is ignored within the :has()
. This might be an edge case, but without this specificity, you can't utilize all that :has()
can offer. We hope that once this is fully supported, :has()
will continue to evolve to be more flexible and will someday be more useful.
A real CSS property for screen readers
Accessibility is more important than ever and is a core focus for all our projects. The web is for everyone, and just as we strive to give the same experience to all users and their abilities, we should hope for the same thing from the code we write.
Unfortunately, CSS is still lacking a lot in this department. Perhaps the most glaring example is how to hide a block of text (visually) that is still accessible to screen readers. Currently, the only solid way to do something like this is with this amalgamation of CSS properties below, which many developers often shove into a .visually-hidden
class.
.hidden-text-block {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
clip-path: inset(50%);
border: 0;
}
There has to be a better way to do this. Why couldn't we leverage the idea of .visually-hidden
and turn it into its own property?
.hidden-text-block {
visually-hidden: true;
}
There doesn't seem to be a lot of progress within the CSS Working Group on this. Still, we hope that as accessibility continues to move to the forefront of working with CSS, there will be more significant movement on this issue.
The color-contrast() property
Continuing along the accessibility track, the color-contrast
property takes a specified color
property and then compares it against two sets of colors, selecting the ones with the highest contrast.
For instance, this compares #404040
and #5a5a5a
against a color of #111
and then picks the higher contrast one.
.header-color {
color-contrast: (#404040 vs #5a5a5a, #111);
}
You could see how this could work really well with a set of :root
color variables versus a variety of background colors, giving the ability to keep colors accessible without any rewritten styles or other JS plugins.
--primary-color: blue;
--secondary-color: green;
--main-bg: #4a4a4a;
--sidebar-bg: #32a3fa;
.text-color {
color-contrast: (--main-bg vs --sidebar-bg, --primary-color);
}
Unfortunately, this property is still experimental, and as of 2022, no browser supports it. Let's hope for some progress on this requested property from the CSS WG. You can learn more about it from their draft spec.
Change non-animatable properties at the beginning/end of an animation
CSS animations are amazing. A quick browse of CodePen reveals some of the absolute jaw-dropping things that brilliant front-end developers can do with CSS alone. But that doesn't mean there isn't room for improvement.
For instance, you can use @keyframes
to control the steps of an animation very easily, and a common method is to fade something from visible to invisible (or vice versa) over a series of frames. This transition works great as a visual presentation, but the effect is not the same for screen readers.
The problem is that a normal show/hide CSS property, such as display: none
or visibility: hidden
doesn't get considered at the start/end of an animation.
.ghost-kitty {
inline-size: 2rem;
block-size: 4rem;
background: url("../images/ghost_cat.jpg") no-repeat center;
animation: ghost-kitty 5s infinite;
}
@keyframe ghost-kitty {
0% {
opacity: 100%;
}
25% {
opacity: 75%;
}
50% {
opacity: 50%;
}
75% {
opacity: 25%;
}
100% {
opacity: 0%;
display: none;
}
}
Using the example above, our .ghost-kitty
will disappear (for good) once it hits the display: none
portion of the keyframe step, but it would still be visible to a screen reader because it just gets rendered at the first usage as it works down the document, which defeats the purpose of an animation. And because opacity
, which is commonly used in animations, does not affect screen readers, you are stuck without a solution in CSS to truly hide/show something for all audiences.
It's possible that as :has()
and :is()
and :where()
grow in usage, there might be some weird hack to make this work (like line-clamp
), but having a useful way to truly control non-animatable properties within an animation would be nice if only to put less JavaScript out there in the world!
text-wrap: balance
Discussed as part of the Text 4 spec for CSS, this would work the same way as wrap
for an inline box, where it would try to eliminate as much empty space as possible - without increasing the number of lines - by considering the amount of remaining space within the block container.
In practice, it would basically wrap text so that each line has (approximately) the same length. So with text-wrap: balance
, this:
Checkout this really long line of text, boy does it go on for a while huh? I wonder
when it will end? Who knows, but it sure is long!
would become
Checkout this really long line of text, boy does it go on for a while
huh? I wonder when it will end? Who knows, but it sure is long!
The best part is that this would work with fluid columns when the length of a column is unknown, so this would work wonders with grid and other responsive design elements.
text-wrap: balance
is just a requested spec for now, but let's keep our fingers crossed that it makes it in sooner rather than later!
Break up box-shadow so its styles can be set separately
This feature is more of a want to have than a need, but it would be nice to declare each box-shadow value independently. Much like being able to declare a border either by shorthand, such as border: 1px solid rebeccapurple
or individually, like:
border-width: 1px;
border-style: solid;
border-color: rebeccapurple
The ability to declare box-shadow values like this would be beneficial:
box-shadow-color: rebeccapurple;
box-shadow-offset-x: 2px;
// Comments like this
Seriously, why are we stuck with /* */
as our only option?
Conclusion
We may never get every little feature we want in CSS. Still, the continuing evolution of it as a language and the new features from the last few years are enough to excite almost any front-end developer.
To keep up with the latest and greatest and what to get excited about next, check out these resources:
- The ShopTalk Show podcast: Hear Jen Simmons talk about what's coming to CSS, including
:has()
, container queries, and more! (Note that this episode is more Safari-centric. Still, we can get excited about these things coming to all browsers!) - The Syntax podcast: There's a recent (as of August 2022) episode about upcoming CSS proposals that may or may not see the light of day, such as
@when
and carets. Intrigued? Give this episode a listen! They also have an episode on:has()
and about new viewport units, which is worth your time. - The CSS Weekly newsletter: A compilation of some of the best articles written by many experts in the community, including Sara Soueidan, Michelle Barker, Jen Simmons, Una Kravets, Jhey Tompkins, Adam Argyle, and more! Subscribe to keep up with the latest and greatest in the CSS world, plus information about potential new CSS changes and features coming down the pipeline.
- The CSS Working Group: If you want to get into the weeds with what's coming down the road for CSS, check out the CSS working group's blog or the GitHub issue list.