DEV Community

Cover image for CSS: The Good Parts
Yoav Ganbar for Builder.io

Posted on • Originally published at builder.io on

CSS: The Good Parts

Back in the day, CSS was like a fresh breath, just letting you style a page in a simple, chill way.

It was about setting rules and letting the browser do its thing. You could change up the margins, fonts, and sizes, but that was just scratching the surface, you know?

The real gem was that 'cascade' thing, letting styles inherit and override others, making for some dynamic, cool pages. Fast forward to today, CSS is like a Swiss army knife for web design. It's got the power to animate, transform, and adapt layouts with flexbox and grid, making it all responsive and cool.

From basic styles to complex animations, CSS has evolved into a whole new level of cool. It's not just about simple styling anymore, it's about bringing your whole web game to life.

Let's dive into how CSS got to where it is today (or scroll down to the last section to look into the future 🔮…).

CSS selectors - the evolving conduits of style

A CSS selector is like a precise instruction in a game of tag. It's a rule that identifies which HTML elements to style. Whether you're pointing to a <div>, .class, or #id, selectors are the messenger of your style declarations, ensuring the correct elements get "tagged".I want you to journey back with me to the early days of CSS. To an era when web design was fresh, raw, and in many ways, restrictive. Remember the old HTML tags like font and center? We used them because we had to, not because we wanted to. And then, like a superhero in a 90s comic book, CSS arrived, and with it came the power of selectors. The original CSS selectors were as basic as the HTML they styled:

h1 {
  color: blue;
}
Enter fullscreen mode Exit fullscreen mode

It was simple, effective, but very limited. This was like trying to paint the Sistine Chapel with crayons.

To add more flexibility, CSS2 introduced new selectors like child (>), adjacent sibling (+), and attribute selectors ([attr=value]). These allowed for more targeted styling:

/* Child Selector */
div > p {
  color: red;
}

/* Adjacent Sibling Selector */
h1 + p {
  margin-top: 20px;
}

/* Attribute Selector */
input[type="text"] {
  width: 200px;
}
Enter fullscreen mode Exit fullscreen mode

These selectors let us express more complex relationships between elements and made our stylesheets more efficient and organized. It was a step forward, but we still needed more.

Enter CSS3. It expanded the CSS selector repertoire with more powerful tools, such as the general sibling combinator (~), the :not() pseudo-class, and a host of attribute selectors:

/* General Sibling Combinator */
h1 ~ p {
  font-size: 1.2em;
}

/* :not() Pseudo-class */
div:not(.highlighted) {
  opacity: 0.5;
}

/* Attribute Selectors */
a[href*="google"] {
  background: url(/images/google-icon.png) no-repeat;
}

Enter fullscreen mode Exit fullscreen mode

We were no longer just styling elements; we were engaging with them, probing their attributes, their relationships with each other. We began crafting sophisticated designs that responded to the content's structure and meaning.

Untitled

CSS3 has brought us pseudo-classes like :nth-child, :nth-of-type, :checked, and ::before and ::after pseudo-elements. Our crayons have become a full artist's palette, and the canvas of the web is richer for it.

/* :nth-child Selector */
li:nth-child(odd) {
  background: lightgray;
}

/* :checked Pseudo-class */
input[type="checkbox"]:checked {
  border-color: green;
}

/* ::before and ::after Pseudo-elements */
blockquote::before {
  content: "❝";
  font-size: 3em;
}
blockquote::after {
  content: "❞";
  font-size: 3em;
}

Enter fullscreen mode Exit fullscreen mode

Another selector worth mentioning is the :is pseudo-class. It allows you to group multiple selectors in one statement, reducing repetition in your code and enhancing readability. For a deeper dive, check out “Simpler CSS Selectors With :is()” by Steve.

Last mention, the :where selector, which is similar to :is. However, the key difference is that :where always has 0 specificity.

Selectors have given us the tools to express our creative vision in code. They continue to evolve, driving the web forward into ever more exciting frontiers of design.

The cascade — leveraging specificity and inheritance

The cascade is a defining feature of CSS and, when harnessed properly, can make your stylesheets more efficient and easier to maintain. It refers to the process of combining different stylesheets and resolving conflicts between different CSS rules that apply to the same element.

The concept of specificity plays a crucial role here. An ID selector has higher specificity than a class selector, which has higher specificity than a type selector.

#header {
  color: blue; /* This will apply because ID selectors have the highest specificity */
}

.container .header {
  color: red; /* This won't apply to the element with id "header" */
}

header {
  color: green; /* This won't apply to the element with id "header" */
}
Enter fullscreen mode Exit fullscreen mode

Knowing to work with the cascade, and not against it will save you a ton of problems. Using tools such as a specificity calculator can help go a long way.

Flexibility with media queries

One of the key strengths of CSS is its built-in responsiveness through media queries. Media queries help you to apply different styles for different devices or screen widths.

@media only screen and (max-width: 600px) {
  body {
    background-color: lightblue;
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the background color of the body changes to light blue when the screen width is 600px or less. This makes CSS a major player in creating responsive designs.

Let's take a spin down memory lane and see how media queries in CSS have been keeping things fresh:

  • 1994: Our main man Håkon Wium Lie lays down the first idea of media queries. It's the start of something big!
  • 1998: CSS2 steps up to the plate and gives us the first taste of media queries.
  • 2001: CSS3 comes on the scene, leveling up media queries with some dope new features.
  • 2012: Media queries hit the big time! They become a recommended standard by the W3C.
  • Right now: Media queries are running things in all the major browsers and have become a key tool in the game of responsive web design.

Power of animations and transitions

With CSS3, animations and transitions have become an integral part of the modern web, creating a dynamic user experience. You can animate changes to CSS properties over time, control the speed of transitions, and create keyframe-based animations.

button {
  transition: background-color 0.5s ease;
}

button:hover {
  background-color: blue;
}
Enter fullscreen mode Exit fullscreen mode

In this snippet, when you hover over a button, its background color transitions to blue over a half-second period.

Embracing the magic of CSS variables (custom properties)

The CSS Working Group had been aware of the need for CSS variables since its inception in 1997. By the late 2000s, developers had created various workarounds like custom PHP scripts and preprocessors like Less and Sass to compensate for this deficiency.

Recognizing that a built-in solution would streamline the process, the group released the first draft of the CSS variables module in 2012. Renamed as CSS custom properties for cascading variables, it gained widespread browser support by 2017.

Gone are the days of static CSS where updating values was a manual, time-consuming chore. Now, we've got CSS variables in our toolkit, letting us store and reuse specific values throughout our stylesheets. These variables ensure consistency and make updates a breeze.

Here's a taste of CSS variables in action:

:root {
  --brand-color: #32a852;
}

body {
  background-color: var(--brand-color);
}

/* On hovering over the body, the brand color changes */
body:hover {
  --brand-color: #a83258;
}

Enter fullscreen mode Exit fullscreen mode

Hover over the body, and voila! Your site's look gets a complete makeover. Now that's CSS variables for you!

Layouts throughout the ages

CSS layouts have gone through a lot of changes over the years. Developers used to create layouts with tables and floats, which were hard to maintain and not very responsive. Later, the introduction of media queries, flexbox, and grid revolutionized the way developers create layouts, making them more responsive and easier to maintain. Let’s dig in.

Transition from table-based layouts to CSS

Stepping into the early 2000s, the era of table-based layouts was starting to fade. Remember those times? When we used table, tr, and td to arrange everything on the page, even the layout. Ah, those were some days!

<table>
  <tr>
    <td>Header</td>
  </tr>
  <tr>
    <td>Main Content</td>
    <td>Sidebar</td>
  </tr>
  <tr>
    <td>Footer</td>
  </tr>
</table>
Enter fullscreen mode Exit fullscreen mode

It was a time when we bent HTML to our will, using it for something it wasn't meant for - the layout. But hey, we made it work, right? But let's be real, it was a pain. Code was hard to maintain, accessibility was compromised, and responsiveness was a far-off dream. We needed a change, and CSS was that change!

The age of float and the clearfix hack

Ahh, the age of floats. I can almost see the nostalgic smiles and frustrated grimaces on your faces, dear readers. You see, before flexbox came and made our lives a lot easier, we were stuck in Floatsville.

Invented as a simple method for wrapping text around images (think newspaper layouts), floats became an unexpected tool for creating entire web layouts.

.column {
  float: left;
  width: 50%;
}
Enter fullscreen mode Exit fullscreen mode

And just like that, we had a two-column layout. Easy enough, right? But the problems arose when we tried to add more elements below our floated ones. Suddenly, our footers were on a trip of their own, snuggling up next to content higher up in the DOM. Oh, the chaos!

Untitled

This was due to a peculiar trait of floated elements. They are partially removed from the normal document flow, meaning elements that follow them in the markup would behave as if the floated element wasn't there.

To fix this, we had to resort to what we now fondly (or not so fondly) refer to as the "clearfix hack". This hack forces the container to expand to contain the floats by creating a new block formatting context.

Here's the famous clearfix hack that's saved many a layout:

.group:after {
  content: "";
  display: table;
  clear: both;
}
Enter fullscreen mode Exit fullscreen mode

By adding an :after pseudo-element to the container, giving it display: table; and clear: both;, we effectively cleared the float. Suddenly, our footers were back where they belonged, and all was right in the world.

Despite its quirks and unexpected behaviors, mastering floats was a rite of passage for every web developer. It taught us the importance of understanding the CSS box model, the document flow, and the weird and wonderful ways CSS could behave. It was a challenging, sometimes hair-pulling experience, but it was a crucial stepping stone on the path to the CSS we know and love today.

New age layouts with flexbox and grid

The biggest two major game-changers that have improved web dev immensely are: flexbox. These bad boys totally flipped the script on layout design.

First up, flexbox. Introduced in CSS3, flexbox was a straight-up revolution for alignment, direction, order, and size of our boxes. No more float and positioning headaches, y'all. flexbox made it simple to create flexible, responsive layouts with less code and more control. Here's a lil' code sample to show you how it's done:

.container {
  display: flex;
  justify-content: space-between;
}

.item {
  flex: 1;
}
Enter fullscreen mode Exit fullscreen mode

In this example, we've got a container set to display: flex; which lets its children know they're playing in a flex context. justify-content: space-between; is keeping our items nicely spaced out. Then we hit the items with flex: 1; to make 'em all equal width, filling up the full space of the container. Clean and simple.

Then we got grid, the next big leap. Grid layout, introduced around 2017, took CSS layouts to a whole new level, letting us define both columns and rows at the same time. CSS grid lets us create complex, two-dimensional layouts that were a real pain to pull off before. Here's a taste:

.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-gap: 10px;
}

.item {
  grid-column: span 2;
}
Enter fullscreen mode Exit fullscreen mode

In this piece, .container is our grid container. We define three equal-width columns with grid-template-columns: repeat(3, 1fr); and set a 10px gap between them with grid-gap: 10px;. Then for our items, we use grid-column: span 2; to make an item span two columns. Now that's power!

You can become a real CSS grid wizard if you look into the grid-template-areas property.

Remember the struggle of centering elements both vertically and horizontally? The combination of different properties such as margin , position , top , left , and transform was enough to make anyone's head spin.

.container {
  position: relative;
}

.element {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
Enter fullscreen mode Exit fullscreen mode

Fast forward to today, and flexbox makes centering a piece of cake:

.container {
  display: flex;
  justify-content: center;
  align-items: center;
}
Enter fullscreen mode Exit fullscreen mode

In the past, creating complex layouts often meant resorting to floating elements, which could be finicky and difficult to manage. Here's a simplified example of a two-column layout using floats:


.container::after {
  content: "";
  display: table;
  clear: both;
}

.column {
  float: left;
  width: 50%;
}
Enter fullscreen mode Exit fullscreen mode

Today, with CSS Grid, you can create complex layouts with minimal code, and without the headaches:

.container {
  display: grid;
  grid-template-columns: 1fr 1fr;
}
Enter fullscreen mode Exit fullscreen mode

Here’s a more robust and complex layout example:

A peek into the near future

There are several upcoming features and improvements in CSS that are already stirring up excitement in the web design and development community. You can find a detailed list in “What’s new in CSS and UI”, one of the latest posts by the Chrome team.

Below are some features I’m excited about:

Container queries

💡 Not supported in Firefox & Safari yet

The ability to style a child and control layouts within layouts. You can change elements based on their available space, as can be seen below:

You can play with the above example code in this Codepen.

Due to the container query, the style is dynamic. Changing the size of the viewport triggers a change for each individual element according to the space they have.

The syntax is a bit similar to media queries, except you just define the styles you want in case the container size meets a condition:

Untitled

This is how it looks like in practice:

/* Create a containment context */
.post {
  container-type: inline-size; /* size & normal are valid values as well */
}

/* Default heading styles for the card title */
.card h2 {
  font-size: 1em;
}

/* If the container is larger than 700px */
@container (min-width: 700px) {
  .card h2 {
    font-size: 2em;
  }
}
Enter fullscreen mode Exit fullscreen mode

Style queries

💡 Not supported in Firefox & Safari yet

Query the style values of a parent container:

<li class="card-container" style="--sunny: true;">
  <div class="weather-card">
    <div class="day">Saturday</div>
    <div class="date">February <span>12</span></div>
    <div class="temps">
      <div class="high">High: <span>55</span></div>/
      <div class="low">Low: <span>47</span></div>
    </div>
    <div class="features">
      Clear skies, sun
    </div>
  </div>
</li>

<style>

.card-container {
  container-name: weather;
}

/* In case the custom propery --sunny: true; change the child */

@container style(--sunny: true) {
 .weather-card {
   background: linear-gradient(-30deg, yellow, orange);
 }

 .weather-card:after {
   content: url(<data-uri-for-demo-brevity>);
   background: gold;
 }
}

</style>
Enter fullscreen mode Exit fullscreen mode

:has pseudo-class

💡 Not supported in Firefox yet.

A way to style an element based on its descendants. Basically, you can apply styles according to its children, which means it can act as the elusive parent selector. However, you can style the children within the parent as well.

<article>
 <h1>Hello</h1>
 <h2>World</h2>
</article>

<style>
/* style parent according to children */
article:has(h1) {
 background: lightgray;
}

/* style child by parent content */
article:has(h1) h2 {
   color: yellow;
}

/* style sibling by adjacent element */
h1:has(+ h2) {
 color: hotpink;
}
</style> 
Enter fullscreen mode Exit fullscreen mode

text-wrap: balance

💡 Currently only supported in Chromium

This new value, like its name, will allow balancing your text, so you don’t have to use JS for this anymore. Adding this to a text block will really make your designers happy.

Nesting

💡 Not supported in Firefox yet

Finally, like SASS and Less, nest and co-locate the styles related to your selector:

.parent {
  color: blue;

  .child {
    color: red;
  }
}
Enter fullscreen mode Exit fullscreen mode

Furthermore, you can also nest media queries (and container queries):

.card {
  display: flex;
  gap: 1rem;

  @media (width >= 480px) {
    display: grid;
  }
}
Enter fullscreen mode Exit fullscreen mode

Alternatively, the first example could be written as such:

.parent {
  color: blue;

  & .child {
    color: red;
  }
}
Enter fullscreen mode Exit fullscreen mode

For more info, I suggest checking out this post by Adam Argyle.

Subgrid

💡 Supported in Firefox and Safari, and under a flag in Chrome

The missing piece to grid, apply grid layouts to a grid item's children, resulting in more consistent and maintainable layouts. It is used by adding either grid-template-rows or grid-template-columns properties with the subgrid value:

<div class="grid">
  <div class="item">
    <div class="subitem"></div>
  </div>
</div>

<style>
/* some styles removed for brevity */
.grid {
  display: grid;
  grid-template-columns: repeat(9, 1fr);
  grid-template-rows: repeat(4, minmax(100px, auto));
}

.item {
  display: grid;
  grid-column: 2 / 7;
  grid-row: 2 / 4;
  grid-template-columns: subgrid;
  grid-template-rows: subgrid;
  background-color: #ffd8a8;
}

.subitem {
  grid-column: 3 / 6;
  grid-row: 1 / 3;
  background-color: rgb(40, 240, 83); /* green */
}
</style>
Enter fullscreen mode Exit fullscreen mode

This would result as so:

Untitled

Scoped CSS

💡 Still in working draft

Specify the boundaries for which specific styles apply, essentially creating native name-spacing in CSS:

@scope (.card) {
  /* only affects a .title that is within a .card */
  .title { 
    font-weight: bold;
  }
}
Enter fullscreen mode Exit fullscreen mode

Scroll-driven animations

💡 Still experimental.

Control the playback of an animation based on the scroll position of a scroll container. Again, reduces the JavaScript complexity to create parallax scrolling, reading indicators and more.

Cascade layers (@layer)

Now widely supported, define layers that dictate the order of precedence in case of multiple cascade layers. You can basically order your style sheets by importance:

@layer base {
  a {
    font-weight: 800;
    color: red; /* ignored */
  }

  .link {
    color: blue; /* ignored */
  }
}

@layer typography {
  a {
    color: green; /* styles *all* links */
  }
}

@layer utilities {
  .pink {
    color: hotpink; /* styles *all* .pink's */
  }
}
Enter fullscreen mode Exit fullscreen mode

View transitions

💡 Not supported in Firefox and Safari

Allows changing the DOM in a single step, while creating an animated transition between the two states. No more need for SPAs (Single Page Apps) to get this done.

There is a need for a bit of JavaScript:

function spaNavigate(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    updateTheDOMSomehow(data);
    return;
  }

  // With a transition:
  document.startViewTransition(() => updateTheDOMSomehow(data));
}
Enter fullscreen mode Exit fullscreen mode

And then CSS takes over:

@keyframes slide-from-right {
  from { opacity: 0; transform: translateX(75px); }
}

@keyframes slide-to-left {
  to { opacity: 0; transform: translateX(-75px); }
}

::view-transition-old(root) {
  animation: 350ms both slide-to-left ease;
}

::view-transition-new(root) {
  animation: 350ms both slide-from-right ease;
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

The future of CSS holds great potential for simplifying complex tasks, improving performance, and enabling developers to create immersive experiences.

As CSS evolves, we may witness the emergence of advanced features that blur the line between CSS and JavaScript, offering native solutions for tasks currently reliant on JS libraries.

Additionally, more comprehensive CSS frameworks could arise, leveraging these new capabilities.

Staying informed about the latest CSS developments is crucial, given its ongoing importance in web design and development. Keeping an eye on updates from the CSS Working Group, following industry leaders, and exploring new features in browser previews will help you stay up to date.

Embrace the exciting possibilities ahead, continue learning, and actively contribute to shaping the future of the web.

Visually build with your components

Builder.io is a headless CMS that lets you drag and drop with your components right within your existing site.

Try it out Learn more

// Dynamically render your components
export function MyPage({ json }) {
  return <BuilderComponent content={json} />
}

registerComponents([MyHero, MyProducts])
Enter fullscreen mode Exit fullscreen mode
Read the full post on the Builder.io blog

Top comments (4)

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Really cool article, but I'm a bit sad to see how short the section on scope is. One of the main things that sets @scope apart from just nesting is the ability to set a lower boundary for the scope, and a smaller but still important feature is that scope proximity acts as a tiebreaker between rules of the same specificity, which is also really cool.

Collapse
 
hamatoyogi profile image
Yoav Ganbar

I agree scope is really cool! But had to "scope" the length of the post 😅
Most def planning on doing deep dives for each new thing soon!

Collapse
 
fruntend profile image
fruntend

Сongratulations 🥳! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up 👍

Collapse
 
davboy profile image
Daithi O’Baoill

Nice, thanks 👍