Journal
Using the :has pseudo-class for real
By day I’m currently working on our Design System’s Table component. In order to achieve a design spec where the table has no bottom-border I needed to set:
- all cells in the final row of the
<tfoot>to have no bottom-border; but also - if there is no
<tfoot>then set all cells in the final row of the<tbody>to have no bottom-border.
Modern CSS’s support for writing selectors which traverse the DOM up, down and sideways is pretty amazing here.
I’ve gone with:
tfoot > :last-child td, tbody:not(:has(+ tfoot)) > :last-child td { border-bottom-width: 0; }
(Some BEM stuff renamed for brevity but that’s the gist of it)
In the past we’ve had to bloat the backend layer with complex and awkward logic that adds “convenience classes” like .fe-Table-bodyLastRow but as Eric Meyer has been saying :has() in particular is going to remove the need for those convenience classes.
Hat-tip to Jhey Tompkins for his excellent recent article on :has which was a great help.
How to Fix Common iOS Accessibility Issues | Deque
Although I don’t work on native apps, I’ve recently been wondering about how accessibility considerations for them compare to those for websites. So this is a timely and useful reference.
iOS provides a lot of accessibility behavior for free, so it’s a great start to making a mobile application accessible. Unfortunately, accessibility is more complicated than the iOS behavior can address, and using only default behavior can actually cause the app to have additional issues.
Tabs: truth, fiction and practical measures
My colleague Anda and I just had a good conversation about tabs, and specifically the company’s tabs component. I’ve mentioned before that our tabs are unconventional and potentially confusing, and Anda was interested to hear more.
What’s the purpose of a tabbed interface?
A tabbed interface is a space-saving tool for collapsing parallel content into panels, with one panel visible at a time but all accessible on-demand. While switching between tab panels the user is kept within the same wider context i.e. the same page, rather than being moved around.
Conventional tabbed interfaces
Here are some great examples of tabs components.
- Inclusive Components – Tabbed Interfaces
- Tabs component in GOV.UK Design System
- ARIA Tabs by Adrian Roselli
Tabs are a device intended to improve content density. They should deliver a same-page experience. Activating a tab reveals its corresponding tab panel. Ideally the approach employs progressive enhancement, starting as a basic Table of Contents. There’s quite a lot of advanced semantics, state and interactivity under the hood.
Faux tabs
But in our Design System at work, ours are currently just the “tabs” with no tab panels, and each “tab” generally points to another page rather than somewhere on the same page. In other words it’s a navigation menu made to look like a tabbed interface.
I’m not happy with this from an affordance point of view. Naming and presenting something as one thing but then having it function differently leads to usability problems and communication breakdowns. As the Inclusive Components Tabbed Interfaces page says:
making the set of links in site navigation appear like a set of tabs is deceptive: A user should expect the keyboard behaviors of a tabbed interface, as well as focus remaining on a tab in the current page. A link pointing to a different page will load that page and move focus to its document (body) element.
Confused language causes problems
One real-life problem with our tabs is that they have been engineered as if they are conventional tabs, however since the actual use case is often navigation the semantics are inappropriate.
We currently give each “tab” the ARIA tab role, defined as follows:
The ARIA
tabrole indicates an interactive element inside atablistthat, when activated, displays its associatedtabpanel.
But our tabs have no corresponding tabpanel; they don’t use JavaScript for a single-page experience balancing semantics, interactivity and state as is conventional. They’re just navigation links. And this mismatch of tabs-oriented ARIA within a non-tabs use case will do more harm than good. It’s an accessibility fail.
A stop-gap solution
If content for one or more tabpanel is provided, apply the complicated ARIA attributes for proper tabs. If not, don’t. This means we allow component consumers to either create i) a real tabbed interface, or ii) “a nav menu that looks like tabs” (but without any inappropriate ARIA attributes). I don’t agree with the latter as a design approach, but that’s a conversation for another day!
Tabs in the future
Some clever people involved with Open UI are using web components to explore how a useful tabs element could work if it were an HTML element. Check out the Tabvengers’ spicy-sections component. Again, this is based on the conventional expectation of tabs as a same-page experience for arranging content, rather than as a navigation menu. And I think it’d make sense to stay on the same path as the rest of the web.
Editable table cells
Yesterday the Design System team received a tentative enquiry regarding making table cells editable. I’m not yet sure whether or not this is a good idea – experience and spidey sense tell me it’s not – but regardless I decided to start exploring so as to base my answer on facts and avoid being overly cautious.
In my mind’s eye, there are two ways to achieve this:
- on clicking the cell, the cell content is presented in an (editable) form input; or
- apply the
contenteditableattribute
In both cases you get into slightly gnarlier territory when you start considering the need for a submit button and how to position it.
I don’t have anything further to add at the moment other than to say that if I had to spike this out, I’d probably start by following Scott O’Hara’s article Using JavaScript & contenteditable.
I’d probably also tweet Scott and ask if he can say anything more on his closing statement which was:
I have more thoughts on the accessibility of contenteditable elements, but that will also have to be a topic for another day…
Update 27-09-22: I’ve also remembered that (if I were to pursue Option 1: input within cell) Adrian Roselli has an article on Accessibly including inputs in tables.
Building the main navigation for a website (on web.dev)
learn about semantic HTML, accessibility, and how using ARIA attributes can sometimes do more harm than good.
Alongside all the sound accessibility and hiding-related advice, I also found Manuel’s approach to progressive enhancement interesting. Rather than i) include a hamburger button directly in the DOM and set its initial state to hidden; or ii) create the button element with JavaScript, he instead nests the button in a template element then clones that element with JavaScript. He later tweeted his rationale for this approach:
If JS doesn't work, the markup inside the template won't be rendered on screen and it's more convenient to prepare the markup upfront instead of using document.createElement().
The ability to prepare complex, JS-dependent component markup upfront in declarative HTML rather than recreating it in JavaScript is a compelling argument for his approach. Especially so if you don’t work in JS framework-based systems therefore your components are not written in JavaScript.
Brilliant first day at We Out Here 2022
Had such a good day yesterday @weoutherefest with Tom, Jason and Craig. In the afternoon we checked Enny, Sally Rodgers (A Man Called Adam) and Aletha. We then stopped for a short break to sample the food and drink (Char Sui Vermicelli from the NAM stall and a break from beer for a cracking coffee). Our nighttime choices were Bake, Charlie Dark, Pearson Sound and Alex Nut.
The absolute highlight was Charlie Dark in the forest. Amazing woodland setting and a killer set! Charlie has such great energy both on the decks and on the mic. He played a mix of house, techno, disco and broken biznizz – right up my street! Great mixing too (aided by his old-school lollipop headphone).
Time for Round Two!
Use CSS :has to set root-level styles based on a button’s state
Great tip here from Jhey. He advises using a button with ARIA and a little JavaScript for your dark-mode toggle. And to apply the dark styles, use a CSS selector which targets the :root parent of the button when in “pressed” state and sets a root-level custom property to “on”.
:root:has([aria-pressed=true]) {
--dark:1;
}
Seriously clever stuff!
And aside from the CSS, I really like the way Jhey advocates using a button rather than a form element such as a checkbox for this kind of interface, much like Léonie did recently.
Experimenting with CSS cascade layers
Back in June I attended CSS Day in Amsterdam. One of my favourite talks was The CSS Cascade – A Deep Dive by Bramus van Damme. Bramus covered everything we wanted to know about the cascade but were afraid to ask! And that included an introduction to CSS Cascade Layers – the latest game-changing CSS feature.
I previously enjoyed Stephanie Eckles’s article Getting Started with CSS Cascade Layers so my interest was already piqued. However seeing Bramus’s talk in person really helped bring home the practical benefits of CSS layers.
For example, currently if we have the selectors ul[class] defined early in the “reset” section of our styles and .nav defined later in a “components” section, the former selector wins due to its higher specificity. That’s not what we want – we want our component styles to override our global styles. This has previously led people to hack around the problem by adding extra specificity to the latter selector, or by wrapping :where() around the former to decrease its specificity, neither of which are desirable. With layers we can do:
@layer reset, components;
@layer reset {
ul[class] {
/* set margin to 0 here */
}
}
@layer components {
.nav {
/* give .nav ul custom margins */
}
}
And .nav gets that higher specificity that we want.
I also like that layers align well with an ITCSS approach.
Taking layers for a spin
Cascade Layers are relatively newly-supported in browsers but pretty all-or-nothing in CSS terms to the extent that using them in a progressively-enhanced approach isn’t really an option. So I wouldn’t yet advocate using Layers on an important production website. However there’s nothing to stop me test-driving them on my personal site. Users on old browsers still get the essential content (although very few styles) and for this site, that’s good enough. So I’ve taken the plunge.
And it’s working really well already! Recently while rewriting some CSS I found that if the top of my styles featured a reset which disabled margins on h2 but I also wanted an h2 to take on margins when used within a low-specificity flow utility, the reset style was winning and that was frustrating. But when I moved my reset styles into a reset layer and my layout styles into a layouts layer with the layer order set as reset, layouts then the layout styles win! Really cool.
We use too many damn modals (modalz modalz modalz dot com)
At our fortnightly Accessibility Forum at work, we just had a great discussion about modal dialogues. We started by discussing whether focus should be completely trapped within the modal or if the user should at least have access to the browser toolbar (we decided on the former). We then moved onto a general discussion on the pros and cons of modals, which led me to share MODALZ MODALZ MODALZ with the team.
Adrian Egger’s website presents some interesting suggestions, which I’ll summarise below:
Have you considered using the following alternatives to modals?
- Non-modal dialogs (e.g. toasts): for non-critical interactions that don’t block the user, use these rather than modals
- New page: lead the user to a different page to isolate the interaction without losing access to functionality such as navigation.
- Go inline: present your content inline to be less disruptive.
- Expanding elements: Use expanding elements such as accordions, toolbars, tooltips, or sliding sidebars (or other modeless elements).
- “Undo” patterns: instead of confirmation modals, consider using inline “Undo” option to speed up the user’s interactions.
When all else fails here’s how to get modals right:
Do:
- Make it easy to close: make it simple to get rid of ‐ by escape key, clicking outside the modal window, and a clearly labelled close button.
- Single purpose: limit the interaction to one, straightforward task.
- Keep it short: be brief and concise in your content
- Accessibility: this is fairly self-explanatory — if you can’t make it accessible, don’t use a modal.
Do not:
- Modal inception: avoid opening a modal from a modal.
- Fullscreen modals: you might as well navigate to a new page.
- Multi-step modals: never create a multi-step modal. That way madness lies.
- Self-spawning modals: never present modals unless prompted by the user.
- Marketing modals: seriously, nobody wants that sh*t — especially not your newsletter.
Modals are so hard to do well and present so many possible issues that I think before diving into designing/building one, it’s worth questioning if a modal is really appropriate for your use case.
Putting a full stop on truncation
At work we’ve recently been shown a couple of design proposals where truncation was presented as a solution to the perceived problem of long and unwieldy content, for example a long description in a table cell. However following good discussions, as a wider team we’re now leaning towards avoiding truncation as an approach. Truncation can present accessibility issues and as Karen McGrane says truncation is not a good content strategy. I reckon we should just let long content wrap, and design for that to look OK.
And when natural wrapping doesn’t cut it – like when you’re tackling very long words in confined spaces – reach for overflow-wrap: break-word as suggested in Ahmad Shadeed’s excellent Handling Short And Long Content In CSS.