Paragraphs

Category Image 073

I sure do love little reminders about HTML semantics, particularly semantics that are tougher to commit to memory. Scott has a great one, beginning with this markup:

<p>I am a paragraph.</p>
<span>I am also a paragraph.</span>
<div>You might hate it, but I'm a paragraph too.</div>
<ul>
  <li>Even I am a paragraph.</li>
  <li>Though I'm a list item as well.</li>
</ul>
<p>I might trick you</p>
<address>Guess who? A paragraph!</address>

You may look at that markup and say “Hey! You can’t fool me, only the <p> elements are “real” paragraphs!

You might even call out such elements as divs or spans being used as “paragraphs” a WCAG failure.

But, if you’re thinking those sorts of things, then maybe you’re not aware that those are actually all “paragraphs”.

It’s easy to forget this since many of those non-paragraph elements are not allowed in between paragraph tags and it usually gets all sorted out anyway when HTML is parsed.

The accessibility bits are what I always come to Scott’s writing for:

Those examples I provided at the start of this post? macOS VoiceOver, NVDA and JAWS treat them all as paragraphs ([asterisks] for NVDA, read on…). […] The point being that screen readers are in step with HTML, and understand that “paragraphs” are more than just the p element.


Paragraphs originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

The “Other” C in CSS

Category Image 052

I think it’s worth listening to anything Sara Soueidan has to say. That’s especially true if she’s speaking at an event for the first time in four years, which was the case when she took the stage at CSS Day 2024 in Amsterdam. What I enjoy most about Sara is how she not only explains the why behind everything she presents but offers it in a way that makes me go “a-ha!” instead of “oh crap, I’m doing everything wrong.”

(Oh, and you should take her course on Practical Accessibility.)

Sara’s presentation, “The Other ‘C’ in CSS”, was published on YouTube just last week. It’s roughly 55 minutes of must-see points on the various ways CSS can, and does, impact accessibility. I began watching the presentation casually but quickly fired up a place where I could take thorough notes once I found myself ooo-ing and ahhh-ing along.

So, these are the things I took away from Sara’s presentation. Let me know if you’ve also taken notes so we can compare! Here we go, there’s a lot to take in.

Here’s the video

Yes, CSS affects accessibility

CSS changes more than the visual appearance of elements, whether we like it or not. More than that, its effects cascade down to HTML and the accessibility tree (accTree). And when we’re talking about the accTree, we’re referring to a list of objects that describes and defines accessible information about elements.

There are typically four main bits of info about an accTree object:

  • Role: what kind of thing is this? Most HTML elements map to ARIA roles, but not all of them.
  • Name: identifies the element in the user interface.
  • Description: how do we further describe the thing?
  • State: what is its current state? Announce it!

The browser provides interactive features — like checking a checkbox that updates and exposes the element’s information — so the user knows what happens following an interaction.

Accessibility tree objects may also contain properties and relationships, such as whether it is part of a group or labeled by another element.

Example: List semantics

CSS can affect an object’s accessible role, name, description, or even whether it is exposed in the accTree at all. As such, it can directly impact the screen reader announcement. We shared a while back how removing list-style affects list semantics, particularly in the case of Safari, and Sara explains its nuances.

/* Removes list role semantics in Safari */
/* Need to add aria-role=list */
ul {
  list-style: none;
}

/* Does not remove role semantics in Safari */
nav ul {
  list-style: none:
}

/* Removed unless specifically re-added in the markup */
ul:where([role="list"]) {
  list-style: none;
}

/* Preserves list semantics */
ul {
  list-style: "";
}

display: contents

CSS can completely remove the presence of an element from the accessibility tree. I took a screenshot from one of Sara’s slides but it’s just so darn helpful that I figured putting the info in a table would be more useful:

Exposed to a11y APIs?Keyboard accessible?Visually accessible (rendered)?Children exposed to a11y APIs?
display: none
visibility: hidden
opactity: 0 and filter: opacity(0)
clip-path: inset(100%)
position(off-canvas)
.visually-hidden
display: contents

The display: contents method does more than it’s supposed to. In short, we know that display controls the type of box an element generates. A value of none, for example, generates no box.

The contents value is sort of like none in that not box is generated. The difference is that it has no impact on the element’s children. In other words, declaring contents does not remove the element or its child elements from the accTree. More than that, there’s a current bug report saying that declaring contents in Firefox breaks the anchoring effect of an ID attribute attached to an element.

Eric Bailey says that using display: contents is considered harmful. If using it, the recommendation is to set it on a generic <div> instead of a semantically meaningful element. If we were to use it on a meaningful interactive element, it would be removed from the accTree, and its children would be bumped up to the next level in the DOM.

Visually hiding stuff

Many, many of us use some sort of .visibility-hidden class as a utility for hiding elements while allowing screenreaders to pick them up and announce the contents. TPGi has a great breakdown of the technique.

.visually-hidden:not(:focus):not(:active) {
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0); /* for IE only */
  clip-path: inset(50%);
  position: absolute;
  white-space: nowrap;
}

This is super close to what I personally use in my work, but the two :not() statements were new to me and threw me for a loop. What they do is make sure that the selector only applies when the element is neither focused nor activated.

It’s easy to slap this class on things we want to hide and call it a day. But we have to be careful and use it intentionally when the situation allows for us to hide but still announce an element. For example, we would not want to use this on interactive elements because those should be displayed at all times. If you’re interacting with something, we have to be able to see it. But for generic text stuff, all good. Skip to content links, too.

There’s an exception! We may want an animated checkbox and have to hide the native control’s appearance so that it remains hidden, even though CSS is styling it in a way that it is visible. We still have to account for the form control’s different states and how it is announced to assistive tech. For example, if we hide the native checkbox for a custom one by positioning it way off the screen, the assistive tech will not announce it on focus or activation. Better to absolutely position the checkbox over the custom one to get the interactive accessibility benefits.

Bottom line: Ask yourself whether an interactive element will become visible when it receives focus when deciding whether or not to use a .visually-hidden utility.

CSS and accessible names

The browser follows a specific process when it determines an element’s accessible name (accName):

  • First, it checks for aria-labelledby. If present, and if the ID in the attribute is a valid reference to an element on the page, it uses the reference’s element’s computed text as the element’s accessible name.
  • Otherwise, it checks for aria-label.
  • Otherwise, unless the element is marked with role="presentation" or role="none" (i.e., the element does not accept an accName anymore), the browser checks if the element can get its own name, which could happen in a few ways, including:
    • from an HTML elemnenty, such as alt or title (which is best on an <iframe>; otherwise, avoid),
    • from another element, like <label> or <legend>, or
    • from its contents.

At this point, Sara went into a brief (but wonderful) tangent on <button> semantics. Buttons are labelable elements and can get their accName by using an aria-label attribute, an aria-labelledby attribute, its contents, or even a <label> element.

ARIA takes precedence over HTML which is why we want to avoid it only where we have to. We can see the priorities and overrides for accessible names in DevTools under the Accessibility tab when inspecting elements.

DevTools exposing the accessibility tree of the document and aria attributes for a selected anchor element.

But note: the order of priority defined in the accName computation algorithm does not define the order of priority that you should follow when providing an accName to elements. The steps should like be reversed if anything. Prioritize native HTML!

CSS generated content

Avoid using CSS to create meaningful content. Here’s why:

<a href="#" class="info">CSS generated content</a>
.info::before {
  content: "ⓘ" / "Info: ";
  /* or */
  content: url('path-to-icon.svg') / "Info: ";
}

/* Contents: : Info: CSS generated content. */

But it’s more nuanced than that. For one, we’re unable to translate content generated by CSS into different languages, at least via automated tools. Another one: that content is gone if CSS is unavailable for whatever reason. I didn’t think this would ever be too big a concern until Sara reminded me that some contexts completely strip out CSS, like Safari’s Reader Mode (something I rely on practically every day, but wish I didn’t have to).

There are also edge cases where CSS generated content might be inaccessible, including in Forced Colors environments (read: color conflicts), or if a broken image is passed to the url() function (read: alt text of the image is not shown in place of the broken image, at least in most browsers, yet it still contributes to the accName, violating SC 2.5.3 Label in Name). Adrian Roselli’s article on the topic includes comprehensive test results of the new feature, showing different results.

Inline SVG is probably better! But we can also do this to help with icons that are meant to be decorative to not repeat redundant information. But it is inconsistent as far as browser implementation (but Sara says Safari gets it right).

/* like: <img src="icon.svg" alt=""> */
.icon {
  content: url('path/to/icon.svg') / "";
}

So, what can we do to help prevent awkward and inaccessible situations that use CSS generated content?

  • Avoid using CSS pseudo-elements for meaningful content — use HTML!
  • Hide decorative and redundant CSS content by giving it an empty alt text (when support is there and behavior is consistent).

CSS can completely strip an element of its accName…

…if the source of the name is hidden in a way that removes it from the accessibility tree.

For example, an <input> can get its accName from a <label>, but that label is hidden by CSS in a way that doesn’t expose it to a11y APIs. In other words, the <label> is no longer rendered and neither are its contents, so the input winds up with no accName.

Showing the HTML for a label-input pair and CSS that uses display: none to hide the label.

BUT! Per spec:

By default assistive technologies do not relay hidden information, but an author can explicitly override that and include hidden text as part of the accessible name or accessible description by using aria-labelledby or aria-describedby.

So, in this case, we can reuse the label even if it is hidden by tacking on aria-labelledby. We could use the .visually-hidden utility, but the label is still accessible and will continue to be announced.

Using aria-labelled by on an text form input with DevTools showing the input's accessible name which is pulled from the label element.

CSS does not affect the state of an element in the accTree

If we use a <button> to show/hide another element, for example, the <button> element state needs to expose that state. Content on hover or focus violates SC 1.4.13 which requires a way to dismiss the content. And users must be able to move their cursor away from the text and have it persist.

CSS-only modals using the checkbox hack are terrible because they don’t trap focus, don’t make the page content inert, and don’t manage keyboard focus (without JavaScript).

Popovers created with the Popover API are always non-modal. If you want to create a modal popover, a <dialog> is the right way to go. I’m enamored with Jhey Tompkins’s demo using the popover for a flyout navigation component, so much so that I used it in another article. But, using popover for modal-type stuff — including for something like a flyout nav — we still need to update the accessible states.

There’s much more to consider, from focus traps to inert content. But we can also consider removing the popover’s ::backdrop for fewer restrictions, like making background content inert or trapping focus. Then again, something like a popover-based flyout navigation violates SC 2.4.12 Focus Not Obscured if it covers or obscures another element with focus. So, yes, visibility is important for usability but we should shoot for better usability that goes beyond WCAG conformance. (Sara elaborates on this in a comment down below.)

So… close the popover when focus leaves it. Sara mentioned an article that Amit Sheen wrote for Smashing Magazine where it’d be wise to pay close attention to how a change is communicated to the user when a <select> menu <option> is selected to update colors on the page. That poses issues about SC 3.2.2 where something changes on input. When the user interacts with it, the user should know what’s going to happen.

Final thoughts

Yeah, let all that sink in. It feels good, right? Again, what I love most about Sara’s presentation (or any of them, for that matter) is that she isn’t pointing any condemning fingers at anyone. I care about oodles accessible experiences but know just how much I don’t know, and it’s practical stuff like this where I see clear connections to my work that can make me better.

I took one more note from Sara’s talk and didn’t quite know where to put it, but I think the conclusion makes sense because it’s a solid reminder that HTML, CSS, and, yes JavaScript, all have seats at the table and can each contribute positively to accessible experience:

  • Hacking around JavaScript with CSS can introduce accessible barriers. JavasScript is still useful and required for these things. Use the right tool for the job.

The “Other” C in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Understanding Gutenberg Blocks, Patterns, and Templates

Category Image 091

Developers suffer in the great multitudes whom their sacred block-based websites cannot reach.

Johannes Gutenberg (probably)

Long time WordPresser, first time Gutenberger here. I’m a fan even though I’m still anchored to a classic/block hybrid setup. I believe Johanes himself would be, too, trading feather pens for blocks. He was a forward-thinking 15th-century inventor, after all.

My enthusiasm for Gutenberg-ness is curbed at the theming level. I’ll sling blocks all day long in the Block Editor, but please, oh please, let me keep my classic PHP templates and the Template Hierarchy that comes with it. The separation between theming and editing is one I cherish. It’s not that the Site Editor and its full-site editing capabilities scare me. It’s more that I fail to see the architectural connection between the Site and Block Editors. There’s a connection for sure, so the failure of not understanding it is more on me than WordPress.

The WP Minute published a guide that clearly — and succinctly — describes the relationships between WordPress blocks, patterns, and templates. There are plenty of other places that do the same, but this guide is organized nicely in that it starts with the blocks as the lowest-level common denominator, then builds on top of it to show how patterns are comprised of blocks used for content layout, synced patterns are the same but are one of many that are edited together, and templates are full page layouts cobbled from different patterns and a sprinkle of other “theme blocks” that are the equivalent of global components in a design system, say a main nav or a post loop.

The guide outlines it much better, of course:

  1. Gutenberg Blocks: The smallest unit of content
  2. Patterns: Collections of blocks for reuse across your site
  3. Synced Patterns: Creating “master patterns” for site-wide updates
  4. Synced Pattern Overrides: Locking patterns while allowing specific edits
  5. Templates: The structural framework of your WordPress site

That “overrides” enhancement to the synced patterns feature is new to me. I’m familiar with synced patterns (with a giant nod to Ganesh Dahal) but must’ve missed that in the WordPress 6.6 release earlier this summer.

I’m not sure when or if I’ll ever go with a truly modern WordPress full-site editing setup wholesale, out-of-the-box. I don’t feel pressured to, and I believe WordPress doesn’t care one way or another. WordPress’s ultimate selling point has always been its flexibility (driven, of course, by the massive and supportive open-source community behind it). It’s still the “right” tool for many types of projects and likely will remain so as long as it maintains its support for classic, block, and hybrid architectures.


Understanding Gutenberg Blocks, Patterns, and Templates originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Another Stab at Truncated Text

Category Image 091

Seems like we’re always talking about clipping text around here. All it takes is a little browsing to spot a bunch of things we’ve already explored.

It’s harder than it looks! And there’s oodles of consideration that go into it! Last time I visited this, I recreated a cool-looking implementation on MDN.

In there, I noted that VoiceOver respects the full text and announces it.

A truncated block of text with VoiceOver text overlay of the full content.

I started wondering: What if I or someone else wanted to read the full text but didn’t have the assistive tech for it? It’s almost a selfish-sounding thing because of long-term muscle memory telling me I’m not the user. But I think that’s a valid request for a more inclusive experience. All folks should be able to get the full content, visually or announced.

I didn’t want to make the same demo, so I opted for something a little different that poses similar challenges, perhaps even more so. This is it:

I know, not the loveliest thing ever. But it’s an interesting case study because:

This setup does what my MDN experiment doesn’t: give users without assistive tech a path to the full content. Right on, ship it! But wait…

Now VoiceOver (I’m sorry that’s all I’ve tested in) announces the button. I don’t want that. I don’t even need that because a screen reader already announces the full text. It’s not like someone who hears the text needs to expand the panel or anything. They should be able to skip it!

But should we really “hide” the button? So much conventional wisdom out there tells us that it’s terrible to hide and disable buttons. Any control that’s there for sighted readers should be present for hearing listeners as well.

If we were to simply drop disabled="true" on the button, that prevents the screen reader from pressing the button to activate something needlessly. But now we’ve created a situation where we’ve disabled something without so much as an explanation why. If I’m hearing that there’s a button on the page and it’s disabled (or dimmed), I want to know why because it sounds like I might be missing out on something even if I’m not. Plus, I don’t want to disable the button by default, especially for those who need it.

This is where “real world” Geoff would likely stop and question the pattern altogether. If something is getting this complicated, then there’s probably a straighter path I’m missing. But we’re all learners here, so I gave that other Geoff a shiny object and how he’s distracted for hours.

Let’s say we really do want to pursue this pattern and make it where the button remains in place but also gives assistive tech-ers some context. I know that the first rule of ARIA is “don’t use ARIA” but we’ve crossed that metaphorical line by deciding to use a <button>. We’re not jamming functionality into a <div> but are using a semantic element. Seems like the “right” place to reach for ARIA.

We could “hide” the button this way:

<!-- NOPE! -->
<button aria-hidden="true">Show Full Text</button>

Buuuut, slapping aria-hidden="true" on a focusable element is not considered a best practice. There’s an aria- approach that’s equivalent to the disabled attribute, only it doesn’t actually disable the button when we’re not using a screen reader.

<button aria-disabled="true">Show Full Text</button>

Cool, now it’s hidden! Ship it. Oh, wait again. Yes, we’ve aria-disabled the button as far as assistive tech is concerned, but it still doesn’t say why.

Button in a focus state with VoiceOver transcription saying 'You are currently on a button. This item is dimmed.'

Still some work to do. I recently learned a bunch about ARIA after watching Sara Soueidan’s “The Other C in CSS” presentation. I’m not saying I “get” it all now, but I wanted to practice and saw this demo as a good learning exercise. I learned a couple different ways we can “describe” the button accessibly:

  • Using aria-label: If our element is interactive (which it is), we can use this to compose a custom description for the button, assuming the button’s accessible name is not enough (which it’s not).
  • Using aria-labelledby: It’s like aria-label but allows us to reference another element on the page that’s used for the button’s description.

Let’s focus on that second one for a moment. That might look something like this:

<button aria-disabled="true" aria-labelledby="notice">Show Full Text</button>
<span id="notice">This button is disabled since assistive tech already announces the article content.</span>

The element we’re referencing has to have an id. And since an ID can only be used once a page we’ve gotta make sure this is the only instance of it, so make it unique — more unique than my lazy example. Once it’s there, though, we want to hide it because sighted folks don’t need the additional context — it’s for certain people. We can use some sort of .visually-hidden utility class to hide the text inclusively so that screen readers still see and announce it:

.visually-hidden:not(:focus):not(:active) {
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0); /* for IE only */
  clip-path: inset(50%);
  position: absolute;
  white-space: nowrap;
}

Let’s make sure we’re referencing that in the CSS:

<button aria-disabled="true" aria-labelledby="notice">Show Full Text</button>
<span id="notice" class="visually-hidden">This button is disabled since assistive tech already announces the article content.</span>

This certainly does the trick! VoiceOver recognizes the button, calls it out, and reads the .visually-hidden notice as the button’s description.

Button in a focus state with VoiceOver transcription saying 'This button is disabled since assistive tech already announces the article content., dimmed, button'

I’m pretty sure I would ship this. That said, the markup feels heavy with hidden span. We had to intentionally create that span purely to describe the button. It’s not like that element was already on the page and we decided to recycle it. We can get away with aria-label instead without the extra baggage:

<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quidem asperiores reprehenderit, dicta illum culpa facere qui ab dolorem suscipit praesentium nostrum delectus repellendus quas unde error numquam maxime cupiditate quaerat?
<button aria-disabled="true" aria-label="This button is disabled since assistive tech already announces the article content.">Read More</button>

VoiceOver seemed to read things a little smoother this time around. For example, it read the aria-label content right away and announced the button and its state only once. Left to my own devices, I’d call this “baked” and, yes, finally ship it.

But not left to my own devices, this probably isn’t up to snuff. I inspected the button again in DevTools to see how the accessibility API translates it.

DevTools accessibility information on a button element with the aria-label filled it matches the name.

This looks off to me. I know that the button’s accessible name should come from the aria-label if one is available, but I also know two other ARIA attributes are designed specifically for describing elements:

  • aria-description: This is likely what we want! MDN describes it as “a string value that describes or annotates the current element.” Perfect for our purposes. But! MDN notes that this is still in the Editor’s Draft of the ARIA 1.3 specification. It shows up widely supported in Caniuse at the same time. It’s probably safe to use but I’m strangely conservative with features that haven’t been formally recommended and would be hesitant to ship this. I’m sure an actual accessibility practitioner would have a more informed opinion based on testing.
  • aria-describedby: We can leverage another element’s accessible description to describe the button just like we did with aria-labelledby. Cool for sure, but not what we need right here since I wouldn’t want to introduce another element only to hide it for its description.

But don’t just take it from me! I’m feeling my way through this — and only partially as far as testing. This also might be a completely dumb use case — hiding text is usually a bad thing, including little excerpts like this. I’m expecting we’ll get some tips from smarter folks in the comments.

Here’s that final demo once again. It’s editable, so feel free to poke around.


Another Stab at Truncated Text originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

In Praise Of The Basics

Fotolia Subscription Monthly 4685447 Xl Stock

Lately, I’ve been thinking about the basics of web development. Actually, I’ve been thinking about them for some time now, at least since I started teaching beginning web development in 2020.

I’m fascinated by the basics. They’re an unsung hero, really, as there is no developer worth their salt who would be where they are without them. Yet, they often go unnoticed.

The basics exist in some sort of tension between the utmost importance and the incredibly banal.

You might even think of them as the vegetable side on your dinner plate — wholesome but perhaps bland without the right seasoning.

Who needs the basics of HTML and CSS, some say, when we have tools that abstract the way they’re written and managed? We now have site builders that require no technical knowledge. We have frameworks with enough syntactic sugar to give your development chops a case of cavities. We have libraries packed with any number of pre-established patterns that can be copy-pasted without breaking a sweat. The need to “learn” the basics of HTML and CSS is effectively null when the number of tools that exist to supplant them is enough to fill a small galaxy of stars.

Rachel Andrew wrote one of my all-time favorite posts back in 2019, equating the rise of abstractions with an increase in complexity and a profound loss of inroads for others to enter the web development field:

“We have already lost many of the entry points that we had. We don’t have the forums of parents teaching each other HTML and CSS, in order to make a family album. Those people now use Facebook or perhaps run a blog on wordpress.com or SquareSpace with a standard template. We don’t have people customising their MySpace profile or learning HTML via Neopets. We don’t have the people, usually women, entering the industry because they needed to learn HTML during that period when an organisation’s website was deemed part of the duties of the administrator.”

— Rachel Andrew, “HTML, CSS and our vanishing industry entry points

There’s no moment more profound in my web development career than the time I changed the background color of a page from default white to some color value I can’t remember (but know for a fact it would never be dodgerblue). That, and my personal “a-ha!” moment when realizing that everything in CSS is a box. Nothing guided me with the exception of “View Source,” and I’d bet the melting Chapstick in my pocket that you’re the same if you came up around the turn of the 21st century.

Where do you go to learn HTML and CSS these days? Even now, there are few dedicated secondary education programs (or scholarships, for that matter) to consider. We didn’t have bootcamps back in the day, but you don’t have to toss a virtual stone across many pixels to find one today.

There are excellent and/or free tutorials, too. Here, I’ll link a few of ’em up for you:

Let’s not even get into the number of YouTube tutorials. But if you do, no one beats Kevin’s incredible archive of recorded gems.

Anyway, my point is that there are more resources than ever for learning web development, but still painfully few entry points to get there. The resources we have for learning the basics are great, but many are either growing stale, are quick hits without a clear learning path, or assume the learner has at least some technical knowledge. I can tell you, as someone who has hit the Publish button on thousands of front-end tutorials, that the vast majority — if not all — of them are geared toward those who are already on the career path.

It was always a bit painful when someone would email CSS-Tricks asking where to get started learning CSS because, well, you’d imagine CSS-Tricks being the perfect home for something like that, and yet, there’s nothing. It’s just the reality, even if many of us (myself included) cut our chops with sites like CSS-Tricks, Smashing Magazine, and A List Apart. We were all learning together at that time, or so it seemed.

What we need are more pathways for deep learning.

Learning Experience Design (LXD) is a real thing that I’d position somewhere between what we know as UX Design and the practice of accessibility. There’s a focus on creating delightful experiences, sure, but the real aim of LDX is to establish learning paths that universally account for different types of learners (e.g., adults and children) and learning styles (e.g., visual and experiential). According to LDX, learners have a set of needs not totally unlike those that Maslow’s hierarchy of needs identifies for all humans, and there are different models for determining those needs, perhaps none more influential than Bloom’s Taxonomy.

These are things that many front-end tutorials, bootcamps, videos, and programs are not designed for. It’s not that the resources are bad (nay, most are excellent); it’s that they are serving different learners and learning types than what a day-one beginner needs. And let’s please not rely on AI to fill the gaps in human experiences!

Like I said, I’ve been thinking about this a lot. Like, a lot a lot. In fact, I recently published an online course purely dedicated to learning the basics of front-end development, creatively named TheBasics.dev. I’d like to think it’s not just another tutorial because it’s a complete set of lessons that includes reading, demonstrations, videos, lab exercises, and assessments, i.e., a myriad of ways to learn. I’d also like to think that this is more than just another bootcamp because it is curricula designed with the intention to develop new knowledge through reflective practices, peer learning, and feedback.

Anyway, I’m darn proud of The Basics, even if I’m not exactly the self-promoting type, and writing about it is outside of my comfort zone. If you’re reading this, it’s very likely that you, too, work on the front end. The Basics isn’t for you exactly, though I’d argue that brushing up on fundamentals is never a bad thing, regardless of your profession, but especially in front-end development, where standards are well-documented but ever-changing as well.

The Basics is more for your clients who do not know how to update the website they paid you to make. Or the friend who’s learning but still keeps bugging you with questions about the things they’re reading. Or your mom, who still has no idea what it is you do for a living. It’s for those whom the entry points are vanishing. It’s for those who could simply sign up for a Squarespace account but want to actually understand the code it spits out so they have more control to make a site that uniquely reflects them.

If you know a person like that, I would love it if you’d share The Basics with them.

Long live the basics! Long live the “a-ha!” moments that help us all fall in love with the World Wide Web.

Shipping Tumblr and WordPress

Category Image 052

Didya see that Tumblr is getting a WordPress makeover? And it’s not a trivial move:

This won’t be easy. Tumblr hosts over half a billion blogs. We’re talking about one of the largest technical migrations in internet history. Some people think it’s impossible. But we say, “challenge accepted.”

Half a billion blogs. Considering that WordPress already powers somewhere around 40% of all websites (which is much, much higher than 500m) this’ll certainly push that figure even further.

I’m sure there’s at least one suspicious nose out there catching whiffs of marketing smoke though I’m amicable to the possibility that this is a genuine move to enhance a beloved platform that’s largely seen as a past relic of the Flickr era. I loved Tumblr back then. It really embraced the whole idea that a blog can help facilitate better writing with a variety of post formats. (Post formats, fwiw, are something I always wished would be a WordPress first-class citizen but they never made it out of being an opt-in theme feature). Tumblr was the first time I was able to see blogging as more than a linear chain of content organized in reverse chronological order. Blog posts are more about what you write and how you write it than they are when they’re written.

Anyway, I know jobs are a scarce commodity in tech these days and Auttomatic is looking for folks to help with the migration.

I was about to say this “could” be a neat opportunity, but nay, it’s a super interesting and exciting opportunity, one where your work is touching two of the most influential blogging platforms on the planet. I remember interviewing Alex Hollender and Jon Robson after they shipped a design update to Wikipedia and thinking how much fun and learning would come out of a project like that. This has that same vibe to me. Buuuut, make no illusions about it: it’ll be tough.


Shipping Tumblr and WordPress originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

:has is an unforgiving selector

Category Image 091

A little thing happened on the way to publishing the CSS :has() selector to the ol’ Almanac. I had originally described :has() as a “forgiving” selector, the idea being that anything in its argument is evaluated, even if one or more of the items is invalid.

/* Example: Do not use! */
article:has(h2, ul, ::-scoobydoo) { }

See ::scoobydoo in there? That’s totally invalid. A forgiving selector list ignores that bogus selector and proceeds to evaluate the rest of the items as if it were written like this:

article:has(h2, ul) { }

:has() was indeed a forgiving selector in a previous draft dated May 7, 2022. But that changed after an issue was reported that the forgiving nature conflicts with jQuery when :has() contains a complex selector (e.g. header h2 + p). The W3C landed on a resolution to make :has() an “unforgiving” selector just a few weeks ago.

So, our previous example? The entire selector list is invalid because the bogus selector is invalid. But the other two forgiving selectors, :is() and :where(), are left unchanged.

There’s a bit of a workaround for this. Remember, :is() and :where()are forgiving, even if :has() is not. That means we can nest either of the those selectors in :has() to get more forgiving behavior:

article:has(:where(h2, ul, ::-scoobydoo)) { }

Which one you use might matter because the specificity of :is() is determined by the most specific item in its list. So, if you need to something less specific you’d do better reaching for :where() since it does not add to the specificity score.

/* Specificity: (0,0,1) */
article:has(:where(h2, ul, ::-scoobydoo)) { }

/* Specificity: (0,0,2) */
article:has(:is(h2, ul, ::-scoobydoo)) { }

We updated a few of our posts to reflect the latest info. I’m seeing plenty of others in the wild that need to be updated, so just a little PSA for anyone who needs to do the same.


:has is an unforgiving selector originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Help choose the syntax for CSS Nesting

Category Image 052

CSS Nesting is making the rounds yet again. Remember earlier this year when Adam and Mia put three syntax options up for a vote? Those results were tallied and it wasn’t even even close.

Now there’s another chance to speak into the future of nesting, this time over at the WebKit blog. The results from the Adam and Mia’s survey sparked further discussion and two more ideas were added to the mix. This new survey lets you choose from all five options.

Jen Simmons has put together a thorough outline of those options, including a refresher on nesting, details on how we arrived at the five options, and tons of examples that show the options in various use cases. Let’s return the favor of all the hard work that’s being done here by taking this quick one-question survey.

To Shared LinkPermalink on CSS-Tricks


Help choose the syntax for CSS Nesting originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Holiday Snowtacular 2022

Category Image 052

We’ve got ourselves a real holiday treat! Join host Alex Trost from the Frontend Horse community for the Holiday Snowtacular 2022 this Friday, December 16.

There’s a lineup of 12 awesome speakers — including Chris Coyier, Cassidy Williams, Kevin Powell, and Angie Jones — each discussing various front-end and web dev topics. It’s like the 12 days of Christmas, but wrapped up in a four-hour session for web nerds like us.

It’s a real good cause, too. The event is free, but includes fundraising Doctors Without Borders with a goal of reaching $20,000. You can donate here any time and anything you give will be matched by the event’s sponors. So, come for the front-end fun and help a great cause in the process.

To Shared LinkPermalink on CSS-Tricks


Holiday Snowtacular 2022 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.