109
loading...
This website collects cookies to deliver better user experience
aria-expanded
, role
, and tabindex
. This is a nifty idea that has been around for a while and is commonly touted as a way to force developers to remember their ARIA attributes and to consider things like semantic markup and focus order/states. I am completely on board with this approach and have previously used it in my own work for exactly the same reasons.aria-expanded
as a styling hook, so if someone forgets it (or misspells it!) then the component just doesn't work until they fix their mistake:.some-component__button[aria-expanded="false"] + .some-component__child {
display: none;
}
<style>
blocks, etc) alongside Tailwind:[role="region"][aria-labelledby][tabindex]:focus {
outline: .1em solid rgba(0,0,0,.1);
}
:nth-child()
, :first-of-type
, or complex selectors then a hybrid approach of utilities and hand-written CSS is often sensible.display
CSS property to an aria-expanded
value.@layer
directive which allows custom CSS to be inserted at the appropriate point in the generated Tailwind CSS file, depending on if it's a base style, a custom utility, or a component specific style. You can also, of course, just have a completely separate CSS file for your non-Tailwind classes – that's what I do at work at the moment. The separate file is managed by Sass and we use it to add custom styles and any Sass dependencies that we need.@tailwind base;
@tailwind components;
@tailwind utilities;
@layer component {
.some-component__button[aria-expanded="false"] + .some-component__child {
display: none;
}
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
/* In reality, such a general selector is probably
a bad idea! But this is just an example… */
button[aria-expanded="false"] + * {
display: none;
}
/* This is a safer selector for things like a div
wrapper around a scrollable/responsive table. */
[role="region"][aria-labelledby][tabindex]:focus {
outline: .1em solid rgba(0,0,0,.1);
}
}
focus:ring-blue-600
class would correspond to this CSS:.focus\:ring-blue-600:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgba(37,99,235,var(--tw-ring-opacity));
}
motion-safe:hover:scale-110
class corresponds to this CSS:@media (prefers-reduced-motion: no-preference) {
.motion-safe\:hover\:scale-110:hover {
--tw-scale-x: 1.1;
--tw-scale-y: 1.1;
transform: var(--tw-transform);
}
}
predecessor-not-expanded
variant that targets the sibling of a button element that has the aria-expanded
attribute value of false
. We could do this with good-old regular CSS using the @layer
directive that I mentioned earlier, or sticking this CSS in a separate CSS file, but for the sake of completeness let's look at how we could do this using the Tailwind API. tailwind.config.js
file, like this example that targets the required
HTML attribute for input
elements.tailwind.config.js
that uses the Tailwind API to create our new predecessor-not-expanded
variant:const plugin = require('tailwindcss/plugin')
module.exports = {
mode: 'jit',
plugins: [
plugin(function ({ addVariant, e }) {
addVariant('predecessor-not-expanded', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `[aria-expanded='false'] + .${e(
`predecessor-not-expanded${separator}${className}`
)}`
})
})
}),
],
}
predecessor-not-expanded:hidden
it will generate CSS that looks like this:[aria-expanded='false'] + .predecessor-not-expanded\:hidden {
display: none;
}
<!-- https://www.w3.org/WAI/GL/wiki/Using_the_WAI-ARIA_aria-expanded_state_to_mark_expandable_and_collapsible_regions#Examples -->
<button
id="button1"
class="bg-purple-100 px-5 py-3 rounded"
aria-controls="topic-1"
aria-expanded="false"
>
<span>Show</span> Topic 1
</button>
<div
id="topic-1"
role="region"
tabindex="-1"
class="predecessor-not-expanded:hidden pt-2"
>
Topic 1 is all about being Topic 1 and may or may not have anything to do with
other topics.
</div>
button
element that precedes it has an aria-expanded
value of false
.group-expanded
variant so we can switch between 'show' and 'hide' text in our component. This will be similar to the group-hover
option in Tailwind already.plugin(function ({ addVariant, e }) {
addVariant('group-expanded', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.group[aria-expanded='true'] .${e(`group-expanded${separator}${className}`)}`
})
})
}),
plugin(function ({ addVariant, e }) {
addVariant('group-not-expanded', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.group[aria-expanded='false'] .${e(`group-not-expanded${separator}${className}`)}`
})
})
}),
group
class to our button
element, and updating the content of the button
to read:<span class="group-not-expanded:hidden">Hide</span><span class="group-expanded:hidden">Show</span> Topic 1
aria-expanded
attribute's value, without writing any new CSS ourselves - the design is odd but it shows how the variant can be used to do whatever you want it to.aria-expanded
attribute value are both controlled by JavaScript, based on the value of the open
property within the component's data object. If we change the value of open
then the class bindings and the ARIA attribute values automatically adjust - we don't need to remember to toggle them manually.button
but before the expandable element) without running into issues where our CSS is completely dependent on our HTML structure never changing.