5 hot new CSS features and how to use them

11. Expand the chat box when it receives focus

So our chat box is currently collapsed and unusable. How can we expand it using only CSS? Time for a new feature – the Focus Container pseudo-class :focus-within.

This is just like the old faithful :focus pseudo-class selector, but it matches if any of the element's descendants have focus. This is interesting because it is the reverse of how CSS usually works, where you can only select elements based on their ancestors. It opens up some interesting potential.

Adding this class will set the max-height of the .messages element to 300px, when anything inside the .chat area receives focus. Remember that an element must accept keyboard or mouse events, or other forms of input, in order to receive focus. Clicking in the <input> will do the job.

.chat:focus-within .messages {
 max-height: 300px; }

12. Take focus further

This is nice, but what else can we do? How about blurring the newsfeed when the chat box has focus? Well with the help of the subsequent-sibling combinator (~) we can do this really easily because the markup for the chat box is before the newsfeed markup:

.chat:focus-within ~ .container {
 filter: blur(5px); }

That's all it takes! We can soften the effect slightly by adding a transition on the filter property to the original .container class. Now we're not suggesting this is a good idea, but we think it's pretty cool that we can do this with CSS alone.

13. Explore placeholder-shown

There are a number of new pseudo-classes described in CSS Selectors Module Level 4. Another interesting one is :placeholder-shown, which matches any input that is showing placeholder text. At the moment our text field doesn't have any placeholder text, so let's add some.

<input type="text" class="input" placeholder="Enter your message">

And then immediately after the text field add a helper message to show the user.

<div class="prompt">Press enter to send</div>

Now add some styling for the helper message, so that by default it is collapsed.

.prompt {
 line-height: 2em;
 max-height: 0;
 overflow: hidden;
 padding: 0 10px;
 text-align: right;
 transition: max-height 500ms; }

14. Make the prompt visible

At the moment the prompt is hidden, so how can we use :placeholder-shown here? Well (most) browsers will display the placeholder text until the field has a value. Just setting focus won't hide the placeholder. This is helpful because we don't want the user to send a blank message anyway, so we can hook into this behaviour to show the prompt only when the user has entered a value. 

Of course in the real world we'd need some validation too. We can use the negation pseudo-class :not to flip the meaning of :placeholder-shown and show the prompt when the placeholder text is not shown (i.e. the field has a value).

.input:not(:placeholder-shown) + .prompt {
 max-height: 2em; }

Setting the max-height to double the 10px font-size, using 2em, will expand the block and the text becomes visible. Neat. Expect to see some clever uses for this seemingly mundane pseudo-class if it makes it through to the final spec.

15. Bring it all to life

We've built the bare bones of a newsfeed with a chat feature using some of the new properties coming to CSS, but at the moment it is lifeless – you can't actually do anything with it. One particularly interesting new feature is CSS Containment, but it is difficult to demo without modifying the DOM. Let's add some super-simple JS to allow us to add messages to our chat box.

First of all we need to add an ID to the <input> and the messages <ul>. We can also add a required attribute to the input too.

<ul id="messages" ...
<input type="text" id="input" required ...

Then create an empty JS file called script.js and add it to the page just before the closing </body> tag so we don't have to wait for DOM ready.

<script src="script.js"></script>
</body>

16. Add a sprinkle of JavaScript

We need to add an event handler to the <input>, which listens for an Enter keypress, validates the field, takes the value (if valid) and adds it to the end of the messages list, clears the field and scrolls to the bottom of the messages. Something like this will do the job for our demo but don't use this in production!

// Finds the important elements
const input = document.getElementById(‘input’);
const messages = document.getElementById(‘messages’);
// Listens for keypress events on the input
input.addEventListener(‘keypress’, (event) => {
  // Checks if the key pressed was ENTER
   if (event.keyCode === 13) {
  // Checks the field is valid
  if (input.validity.valid) {
  // Creates a DOM element using the value
  const message = createMessage(input.value);
  // Appends the new message to the list
  messages.appendChild(message);
  // Clears the field
  input.value = ‘’;
  // Scrolls the messages to the bottom
  messages.parentNode.scrollTop = messages.parentNode.scrollHeight;
  }
  }
});
// Converts value to string of HTML
function createMessage (value) {
  // Warning: Don’t do this in production without sanitizing the string first!
  return stringToDom(`<li><div class=”message”>${value}</div></li>`)
}
// Converts string to real DOM
function stringToDom (string) {
  const template = document.createElement(‘template’);
  template.innerHTML = string.trim();
  return template.content.firstChild; }

Now when you type in the field and press Enter you'll see your message added to the bottom of the element's list. 

17. Add some additional info

In order to demonstrate one of the benefits of the final new CSS property contain we need to do something a little contrived. We're going to implement a feature that displays the time a new message was sent in a box at the top of the messages list. This will appear when you hover your mouse over a message.

First up we need to add this information to our new messages. We can modify the return line of the createMessage function to add the current time to an attribute.

 return stringToDom(`
 <li>
 <div class="message message--mine" data-timestamp="${new Date().toString()}">
 ${value}
 </div>
 </li>
 `);

You might have noticed that new messages have an additional class message--mine. Let's add a quick class so they stand out and align to the right of the list.

.message--mine {
 background: #ff2089;
 margin-left: 20%;
 margin-right: 0;
}

18. Display the timestamp

So our goal is to make the message's timestamp appear at the top of the list of messages, but we need to do it so it is always visible even when the list is scrolled. First off let's try and render the value using a pseudo-element. We only want to do this for our messages. See below.

.message--mine::after {
 content: attr(data-timestamp);
}

That should display the timestamp inside the message. Let's modify it slightly so it only appears on :hover, has a bit of styling and is fixed to the top of the messages area so it is visible even when scrolled.

.message--mine:hover::after {
 background: #000;
 color: #ff2089;
 content: attr(data-timestamp);
 left: 0;
 padding: 5px;
 position: fixed;
 top: 0;
 width: 100%;
}

Hmm, it sort of works but it is appearing at the top of the page rather than the scrollable area. Let's add position: relative to the .messages pane to try and contain it.

.messages {
 ...
 position: relative;
 ... 
}

Ah, it doesn't work either because fixed positioning is anchored to the viewport, not its relative ancestor. Time for our final new property – contain.

19. Explore Containment

CSS Containment is an exciting new proposition. It has a number of options that give you the ability to limit the browser's styling, layout and paint work to a particular element. This is of particular benefit when modifying the DOM. Some changes – even small ones – require the browser to relayout or repaint the entire page, which obviously can be an expensive operation even though browsers work hard to optimise this for us.

Using CSS Containment we can essentially ring-fence parts of the page and say 'what happens in here, stays in here'. This also works the other way around and the ring-fenced element can be protected from changes made outside.

There are four values for contain each with a different purpose. You can combine them as you require.

  • layout – Containing element is totally opaque for layout purposes; nothing outside can affect its internal layout, and vice versa
  • paint – Descendants of the containing element don't display outside its bounds
  • style – Certain properties don't escape the containing element (sounds like the encapsulation you get with the Shadow DOM, but isn't)
  • size – Containing element can be laid out without using the size of its descendants

Alternatively you can use one of two keywords as a kind of shorthand:

  • strict is an alias for ‘layout paint style size'
  • content is an alias for ‘layout paint style'

Each of these values are a little opaque so I would recommend reading the spec and playing around with them in DevTools to see what actually happens. 

The two important values are layout and paint as they offer performance optimisation opportunities when used within complex web apps that require a lot of DOM manipulation. In our rudimentary demo however we can leverage one of the consequences of using contain: paint to help us with the timestamp positioning.

According to the spec, when using paint the "element acts as a containing block for absolutely positioned and fixed positioned descendants". This means we can set contain: paint on the .chat class and the fixed positioning will be based on the card rather than the viewport. You'd get the same effect by using transform: translateZ(0) but containment feels less hacky.

.chat {
 ...
  contain: paint;
... }

20. Wrap it up

That concludes our whistle-stop tour of some new CSS features. Most of them are pretty straightforward, although we'd definitely recommend delving into CSS Containment in more detail. We've only really touched on a small part of it here and it is the kind of feature that sounds like it does something that it actually doesn't – the encapsulation of styling. That said it could prove very helpful, and it is already at the Candidate Recommendation stage, so it's well worth a look.

This article was originally published in creative web design magazine Web Designer. Buy issue 274 or subscribe.

Read more: