Responsive Styling Using Attribute Selectors

One of the challenges we face when implementing class-based atomic styling is that it often depends on a specific breakpoint for context.

<div class="span-12"></div> <!-- we want this for small screens  -->
<div class="span-6"></div>  <!-- we want this for medium screens -->
<div class="span-4"></div>  <!-- we want this for large screens  -->

It’s common to use a prefix to target each breakpoint:

<div class="sm-span-12 md-span-6 lg-span-4"></div>

This works well until we start adding multiple classes. That’s when it becomes difficult to keep a track what relates to what and where to add, remove. or change stuff.

<div class="
  sm-span-12 
  md-span-6 
  lg-span-4 
  sm-font-size-xl 
  md-font-size-xl 
  lg-font-size-xl 
  md-font-weight-500 
  lg-font-weight-700">
</div>

We can try to make it more readable by re-grouping:

<div class="
  sm-span-12 
  sm-font-size-xl 


  md-span-6 
  md-font-size-xl 
  md-font-weight-500 


  lg-span-4 
  lg-font-size-xl 
  lg-font-weight-700">
</div>

We can add funky separators (invalid class names will be ignored):

<div class="
  [
   sm-span-12 
   sm-font-size-xl 
  ],[
   md-span-6 
   md-font-size-xl 
   md-font-weight-500 
  ],[
   lg-span-4 
   lg-font-size-xl 
   lg-font-weight-700
  ]">
</div>

But this still feels messy and hard to grasp, at least to me.

We can get a better overview and avoid implementation prefixes by grouping attribute selectors instead of actual classes:

<div 
  data-sm="span-12 font-size-lg"
  data-md="span-6 font-size-xl font-weight-500"
  data-lg="span-4 font-size-xl font-weight-700"
>
</div>

These aren’t lost of classes but a whitespace-separated list of attributes we can select using [attribute~="value"], where ~= requires the exact word to be found in the attribute value in order to match.

@media (min-width: 0) {
 [data-sm~="span-1"] { /*...*/ }              
 [data-sm~="span-2"] { /*...*/ }   
 /* etc. */ 
}
@media (min-width: 30rem) {
 [data-md~="span-1"] { /*...*/ }   
 [data-md~="span-2"] { /*...*/ }   
 /* etc. */   
}
@media (min-width: 60rem) {
 [data-lg~="span-1"] { /*...*/ }   
 [data-lg~="span-2"] { /*...*/ }   
 /* etc. */   
}

It may be a bit odd-looking but I think translating atomic classes to  attributes is fairly straightforward (e.g. .sm-span-1 becomes [data-sm~="span-1"]). Plus, attribute selectors have the same specificity as classes, so we lose nothing there. And, unlike classes, attributes can be written without escaping special characters, like /+.:?.

That’s all! Again, this is merely an idea that aims to make switching declarations in media queries easier to write, read and manage. It’s definitely not a proposal to do away with classes or anything like that.

The post Responsive Styling Using Attribute Selectors appeared first on CSS-Tricks.

A Complete Guide to Data Attributes

Introduction

HTML elements can have attributes on them that are used for anything from accessibility information to stylistic control.

<!-- We can use the `class` for styling in CSS, and we've also make this into a landmark region -->
<div class="names" role="region" aria-label="Names"></div>

What is discouraged is making up your own attributes, or repurposing existing attributes for unrelated functionality.

<!-- `highlight` is not an HTML attribute -->
<div highlight="true"></div>

<!-- `large` is not a valid value of `width` -->
<div width="large">

There are a variety of reasons this is bad. Your HTML becomes invalid, which may not have any actual negative consequences, but robs you of that warm fuzzy valid HTML feeling. The most compelling reason is that HTML is a living language and just because attributes and values that don't do anything today doesn't mean they never will.

Good news though: you can make up your own attributes. You just need to prefix them with data-* and then you're free to do what you please!

Syntax

It can be awfully handy to be able to make up your own HTML attributes and put your own information inside them. Fortunately, you can! That's exactly what data attributes are. They are like this:

<!-- They don't need a value -->
<div data-foo></div>

<!-- ...but they can have a value -->
<div data-size="large"></div>

<!-- You're in HTML here, so careful to escape code if you need to do something like put more HTML inside -->
<li data-prefix="Careful with HTML in here."><li>

<!-- You can keep dashing if you like -->
<aside data-some-long-attribute-name><aside>

Data attributes are often referred to as data-* attributes, as they are always formatted like that. The word data, then a dash -, then other text you can make up.

Can you use the data attribute alone?

<div data=""></div>

It's probably not going to hurt anything, but you won't get the JavaScript API we'll cover later in this guide. You're essentially making up an attribute for yourself, which as I mentioned in the intro, is discouraged.

What not to do with data attributes

Store content that should be accessible. If the content should be seen or read on a page, don't only put them in data attributes, but make sure that content is in the HTML content somewhere.

<!-- This isn't accessible content -->
<div data-name="Chris Coyier"></div>

<!-- If you need programmatic access to it but shouldn't be seen, there are other ways... -->
<div>
  <span class="visually-hidden">Chris Coyier</span>
</div>

Here's more about hiding things.

Styling with data attributes

CSS can select HTML elements based on attributes and their values.

/* Select any element with this data attribute and value */
[data-size="large"] {
  padding: 2rem;
  font-size: 125%;
}

/* You can scope it to an element or class or anything else */
button[data-type="download"] { }
.card[data-pad="extra"] { }

This can be compelling. The predominant styling hooks in HTML/CSS are classes, and while classes are great (they have medium specificity and nice JavaScript methods via classList) an element either has it or it doesn't (essentially on or off). With data-* attributes, you get that on/off ability plus the ability to select based on the value it has at the same specificity level.

/* Selects if the attribute is present at all */
[data-size] { }

/* Selects if the attribute has a particular value */
[data-state="open"],
[aria-expanded="true"] { }

/* "Starts with" selector, meaning this would match "3" or anything starting with 3, like "3.14" */
[data-version^="3"] { }

/* "Contains" meaning if the value has the string anywhere inside it */
[data-company*="google"] { }

The specificity of attribute selectors

It's the exact same as a class. We often think of specificity as a four-part value:

inline style, IDs, classes/attributes, tags

So a single attribute selector alone is 0, 0, 1, 0. A selector like this:

div.card[data-foo="bar"] { }

...would be 0, 0, 2, 1. The 2 is because there is one class (.card) and one attribute ([data-foo="bar"]), and the 1 is because there is one tag (div).

Attribute selectors have less specificity than an ID, more than an element/tag, and the same as a class.

Case-insensitive attribute values

In case you're needing to correct for possible capitalization inconsistencies in your data attributes, the attribute selector has a case-insensitive variant for that.

/* Will match
<div data-state="open"></div>
<div data-state="Open"></div>
<div data-state="OPEN"></div>
<div data-state="oPeN"></div>
*/
[data-state="open" i] { }

It's the little i within the bracketed selector.

Using data attributes visually

CSS allows you to yank out the data attribute value and display it if you need to.

/* <div data-emoji="✅"> */

[data-emoji]::before {
  content: attr(data-emoji); /* Returns '✅' */
  margin-right: 5px;
}

Example styling use-case

You could use data attributes to specify how many columns you want a grid container to have.

<div data-columns="2"></div>
<div data-columns="3"></div>
<div data-columns="4"></div>

Accessing data attributes in JavaScript

Like any other attribute, you can access the value with the generic method getAttribute.

let value = el.getAttribute("data-state");

// You can set the value as well.
// Returns data-state="collapsed"
el.setAttribute("data-state", "collapsed");

But data attributes have their own special API as well. Say you have an element with multiple data attributes (which is totally fine):

<span 
  data-info="123" 
  data-index="2" 
  data-prefix="Dr. "
  data-emoji-icon="🏌️‍♀️"
></span>

If you have a reference to that element, you can set and get the attributes like:

// Get
span.dataset.info; // 123
span.dataset.index; // 2

// Set
span.dataset.prefix = "Mr. ";
span.dataset.emojiIcon = "🎪";

Note the camelCase usage on the last line there. It automatically converts kebab-style attributes in HTML, like data-this-little-piggy, to camelCase style in JavaScript, like dataThisLittlePiggy.

This API is arguably not quite as nice as classList with the clear add, remove, toggle, and replace methods, but it's better than nothing.

You have access to inline datasets as well:

<img src="spaceship.png"
  data-ship-id="324" data-shields="72%"
  onclick="pewpew(this.dataset.shipId)">
</img>

JSON data inside data attributes

<ul>
  <li data-person='
    {
      "name": "Chris Coyier",
      "job": "Web Person"
    }
  '></li>
</ul>

Hey, why not? It's just a string and it's possible to format it as valid JSON (mind the quotes and such). You can yank that data and parse it as needed.

const el = document.querySelector("li");

let json = el.dataset.person;
let data = JSON.parse(json);

console.log(data.name); // Chris Coyier
console.log(data.job); // Web Person

JavaScript use-cases

The concept is that you can use data attributes to put information in HTML that JavaScript may need access to do certain things.

A common one would have to do with database functionality. Say you have a "Like" button:

<button data-id="435432343">♡</button>

That button could have a click handler on it which performs an Ajax request to the server to increment the number of likes in a database on click. It knows which record to update because it gets it from the data attribute.

Specifications

Browser support

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
7611125.1

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
796835.0-5.1

The post A Complete Guide to Data Attributes appeared first on CSS-Tricks.

Styling Based on Scroll Position

Rik Schennink documents a system for being able to write CSS selectors that style a page when it has scrolled to a certain point. If you're like me, you're already on the lookout for document.addEventListener('scroll' ... and being terrified about performance. Rik gets to that right away by both debouncing the function as well as marking the event as passive.

The end result is a data-scroll attribute on the <html> element that can be used in the CSS. Meaning if you're scrolled to 640px down the page, you have <html data-scroll="640"> and could write a selector like:

html:not([data-scroll='0']) {
  body {
    padding-top: 3em;
  }
  header {
    position: fixed;
  }
}

See the Pen
Writing Dumb JS 🧟‍♂️ and Smart CSS 👩‍🔬
by Rik Schennink (@rikschennink)
on CodePen.

Unfortunately, we don't have greater than (>) less than (<) selectors in CSS for things like numbered attributes, so the CSS styling potential is fairly limited here. You might ultimately need to update the JavaScript function such that it applies other classes or data attributes based on your math. But you'll already be set up for good performance here.

"Apply styles when the user has scrolled away from the top" is a legit use case. It makes me think of a once function (like we have in jQuery) where any scroll event would only be triggered once and then not again. They scrolled! So, by definition, they aren't at the top anymore! But that doesn't deal with when they scroll back to the top.

I find it generally more useful to use IntersectionObserver for styling things based on scroll position. With it, you can do things like, "has this element been scrolled into view or beyond," which is generically useful and can be used for scrolled-away-from-top stuff too.

Here's an example that adds or removes a class if a user has scrolled past a hidden pixel positioned at 500px down the page.

See the Pen
Fixed Header with IntersectionObserver
by Chris Coyier (@chriscoyier)
on CodePen.

That's performant as well, avoiding any scroll event handlers at all.

And speaking of IntersectionObserver, check out "Trust is Good, Observation is Better—Intersection Observer v2".

The post Styling Based on Scroll Position appeared first on CSS-Tricks.

Text Wrapping & Inline Pseudo Elements

I love posts like this. It's just about adding a little icon to the end of certain links, but it ends up touching on a million things along the way. I think this is an example of why some people find front-end fun and some people rather dislike it.

Things involved:

  1. Cool [attribute] selectors that identify off-site links
  2. Deliberating on whether it's OK to use additional HTML within links or not
  3. Usage of white-space
  4. Combining margin-left and padding-right to place icons into placeholder space
  5. Using custom properties to keep things easier
  6. Usage of inline SVG versus background SVG
  7. Considering inline versus inline-block
  8. Using masks

Direct Link to ArticlePermalink

The post Text Wrapping & Inline Pseudo Elements appeared first on CSS-Tricks.