I’m not surprised but a little disappointed that most of the older techniques aren’t mentioned:
- checkbox/radio inputs can be used to toggle state with `:checked ~ .foo` selectors
- `:focus` (and now `:focus-within`, and `:active` though it’s less useful) can be used similarly, but also allow child selection [Edit to add: `tabindex="-1"` makes anything focusable with a pointer input, but doesn’t capture keyboard tab or iteration with assistive tools]
- `:target` can be used similarly, paired with fragment links [Edit to add: but beware history entries, this can be a poor UX]
- `<label>` can be used to not only set those states but also trigger scrolls (including within `scroll-snap` parents) without creating navigation history entries
- the `attr()` function can be used to reference server-dynamic HTML data for display with `content` in pseudo-elements
- I have to assume CSS animations are adopted widely enough that people aren’t using JS for that where it isn’t needed; but you can also use declarative, even interactive, animations in SVG
- speaking of which, inline SVG (even `<use>` references) are part of the CSS cascade, and you can change any CSS-addressable property with `currentColor`
- and you can nest HTML in SVG with `<foreignObject>` if you want to use SVG techniques in HTML
- probably not worth mentioning but in case you don’t know... if you miss table layouts, you can use them with `display` on basically anything; if you want table semantics without tabular rendering you can override `display` as well
Alllllll of that being said, if you use these techniques check your stuff with assistive technologies!
:focus-within is a favourite of mine since it is one of the few CSS selectors where child element state is significant. Thus it is very nice for drop-downs. It also works with CSS transitions, so my pure CSS drop-downs have a 150ms easing in & out (tip: transition the visibility property, since display:none can't be delayed).
There is another, however. An element whose state depends on that of other elements, and it's even more general. A form element moves between :valid and :invalid based on its inputs, allowing us to use form:{in}valid with any of the descendant, child, sibling or adjacent combinators. A hidden required checkbox is sufficient. Radio inputs work too (tip: you can clear radios back to :invalid with an input type="reset").
The really, truly monstrous part of this, however, is that the associated input doesn't even have to be a child of the form. Using the form attribute (i.e., <input form="whatever">) means they can be anywhere on the page, and they can themselves be hidden and targeted from somewhere else on the page again, with a <label> element.
I once documented the horrifying potential of this in a company wiki, along with a lovely modal slideover that was wrapped in a <form> element and transitioned to visible based on form:valid via a hidden required radio button, and whose backdrop was a reset button, and this was rightly labelled NSFW and banned by popular acclaim from ever appearing in our HTML.
Oh my gosh. I’ve read this comment over and over, and didn’t “get it” until it finally clicked just now. You found a way to use form validity as a parent selector! Monstrous for sure, but fucking brilliant and I’m ashamed I haven’t thought of it as I’ve been trying to justify... all manner of things to claw sibling selectors back from things I want to use but div/custom-element wrap in ways I can’t abide.
Try imagining the possibilities when using validation-capable inputs other that checkboxes and radios, too. How about a range slider, or a text input with a validation regex...
For further inspiration, I recommend reading the entirety of the HTML Living Standard and the current CSS Snapshot about once a year. I'm currently excited about the opportunities for [open], particularly with the <details> and <dialog> elements.
My biggest disappoints of the year are with <datalist>, unfortunately, which is so mysteriously half-baked it's practically worthless; followed by Webkit dragging its heels over <dialog> support and customized built-in element support.
Ok, this is a great hack - but what would one practically use this for ? I mean the standard checkbox hack works too right ? Or is there some place where this technique excels ?
There are two bonus capabilities from using the form:valid hack:
1. The input element(s) can now be anywhere on the page. You can, for example, use adjacency to style a control button, without torturing your HTML to squeeze the button into being a sibling of whatever it's activating.
2. You can use form:valid as a parent selector i.e. as a wrapper element. You couldn't use an input:checked as a parent selector because the <input> element's content model is "nothing" i.e. there are no child elements under an <input>, but you sure can with a <form>. The result is less brittle to code-rot.
This is extremely freeing and allows you to use, for example, a :checked adjacency for styling an activation button and a wrapper element (a form:valid) for the large complex component (modal? drop-down? slideover? video content?) that it activates. Heck, it can even be an iframe. Here's the bones of an example:
1. Behaviour of some elements is different inside a <form>. Most obviously, don't nest a <form> inside with the same scope.
2. Your HTML maybe become a mess of scattered <form>, <input>, and <label> elements that many other developers will look at and say "WTF was this person thinking" and "what does this button do?"
I would use it where I don’t have full control over the document structure (for example where my checkbox can’t sibling select because something third party wraps it in a div).
I loved this when I first learned it but was convinced by some accessibility-minded people that it usually doesn’t accurately convey the state to assistive technologies. Instead, use a <button> with an appropriate ARIA attribute conveying a state (pressed, expanded, etc.) that’s toggled by JavaScript. Or use <details> <summary> elements if it’s a disclosure widget.
Yeah, they lied to you. The trouble with ARIA is that it relies, fundamentally, on the assumption that everything's a RIA (the hint is in the name). This assumption is bollocks.
A typical failure scenario is toggles in forms, that appeared to have been set by button-press, but have only changed cosmetically. The silent errors that creep in this way are painful to unravel. Relying on input element state avoids not merely the tail wagging the dog, but the tail wagging only itself because TypeError: dog is not an object.
When overused, or used in a cargo-cult fashion, ARIA is also notorious for making pages less accessible, and unfortunately this is frequently the case.
If you want to build accessible pages, then write simple and semantic HTML. Out of the box this is likely to be fairly accessible from the go. Then annotate using the minimum of JS to set the minimum of ARIA attributes that clearly improve accessibility, and if someone's relying on ARIA then you can, by definition, assume that JS is available, since it is (I'll reiterate this) right there in the name, and what's more if it it's broken or unavailable, then your fallback plain-old-HTML retains some semblance of utility and accessibility for assisted and non-assisted visitors alike.
The path through the swamp doesn't have to be smothering it in concrete.
> annotate using the minimum of JS to set the minimum of ARIA attributes that clearly improve accessibility
Yes, like aria-expanded="true" on a <button> that opens a custom disclosure widget instead of using the CSS-only :checked ~ .foo hack I was responding to. The actual hiding/revealing of the related content can still be done with CSS alone using [aria-expanded="true"].
Here's a bunch of checkbox hack examples [0]. The custom radio buttons and checkboxes and the push toggles are fine (the use-case is fine, I didn't closely examine everything about the implementations), the rest should be done some other way that probably involves buttons with JavaScript.
Custom radio buttons and checkboxes are remarkably easy if you style the <input> it self using `appearance: none`. With multiple background images and masks there is really no need for a complicated sibling selectors and <label> setup:
Also: almost all of these are actively in use on my personal site, they’re not just stuff I mention academically. They’re great for places you might progressively enhance with JS but want to provide reasonable no-JS fallbacks.
I would recommend getting comfortable using the built-in screen readers on your own devices (VoiceOver, Narrator, etc). Lots of people use those same tools. You'll probably just want to mute the screen reader and use the developer option to display what's being announced on screen as text.
Get comfortable navigating web pages using the various functionalities available (the ability to scan only headlines, for example).
I also open the accessibility tab in the web inspector in Chrome or Firefox to see the accessibility tree, which is like a simplified version of the DOM tree that gets exposed to assistive technologies.
As a linux user I do most of my assistive technology testing with Orcas. Do you have any insight into that? Do many assistive technology users use Orcas? Is Orcas behavior significantly different from the more popular once?
In the last WebAIM Screen Reader survey [0], if anyone answered they primarily use Orca, they got lumped into "Other"; out of over 1,000 respondents, 7 primarily use Chromevox so the Orca number must be lower than that. If some use Orca sometimes, they must have been less than 1% of the respondents and got lumped into Other for that question as well.
That doesn't mean that Orca doesn't do a good job or that it works very differently than other screen readers. The A11ySupport site [1] does have some data on Orca's support for specific code. For instance, it fully supports the aria-expanded attribute but has no support for description list elements.
If you have an iOS or Android device, using their built-in screen readers might be more instructive.
> you want to at least test with JAWS, NVDA and VoiceOver
It depends on what the stakes are but if you’re consistently confirming things work with just one screen reader, that makes most of the difference.
NVDA with Firefox or Chrome is probably the best to test with; it’s free, probably the most common choice now, and it has a lot in common with JAWS. But if testing with Windows is inconvenient, testing VoiceOver with Safari for Mac is still very useful, in part because it’s very similar to VoiceOver on iOS.
The most simple way to test accessibility is to only use a keyboard. Use tab ir shift-tab to switch between elements. Tab only browses through links and form elements. Use enter to follow links or buttons.
Simple things like 'outline' on focused or activated elements are easy things to pick up. Elements that are changed by JavaScript should not only change on a mouseclick or touch, but also on pressing Enter or ESC.
One popular case for :checked JavaScriptless visual state is popups of various kinds; these days, many of those can be done with <details>, which is normally semantically better out of the box and generally equally capable (even down to things like “click anywhere outside and it closes” via some full-viewport ::before, on the summary rather than label in this instance).
I‘ve found that SVG animations seem to be kind of slow, especially compared to CSS animations. My suspicion is that specific browsers don’t optimize them in the same way.
CSS animation are usually done with the use of the transform propertie. The transform propertie does not trigger painting, and then can be done with the help of the GPU.
Something to keep in mind about using checkboxs and radios to toggle state changes, CSS tricks in general is that depending on what you're doing they're often not fully accessible. To fully support keyboard input and aria attributes like `aria-expanded` you often need to use JavaScript.
To fully support keyboard input...you often need to use JavaScript.
This seems 100% backward. What happened to tabindex? My experience has been that HTML+CSS (I wouldn't even call most of these "hacks", they are normal intended features of CSS) is much more likely to be navigable by keyboard than anything using JS.
Does anyone know if changing the display type of table elements (table, tr, td, etc.) to `block` or `flex` affect accessibility in any way? I would hope that the semantic structure of the tags would override changes on the styling level, but never been able to find a solid answer.
An accordion widget is simply a series of radio button followed by div. You can see this in action at amazon.com in a product page with multiple purchase options, the buy button will have a radio with multiple options, it's just a radio button. An accordion that allows multiple open panes is a series of checkbox followed by div. More advanced, you can create a flashcard app by allotting two checkboxes per card. I used this technique around 3 years ago when I was studying kanji: http://sokogakuen.framba.ch
<label>
<input class”real-check” type=checkbox />
<span class”fake-check”/>
<strong>this is the text label</strong>
</label>
- visually hide the checkbox
- use a selector like “.real-check:checked + .fake-check and draw whatever you want in the fake check.
- the fake check and the text label are both inside the label element. Interacting with them will toggle the check box.
sure, but if I have an actual table with tabular data, it would be nice to be able to just hide a row and adjust colspan in css as well as necessary. it's much less of a problem than it used to be these days, since grid is very well supported now, but still kind of annoying to rebuild the entire table into a grid.
You can! CSS Grid works just fine with <table> and its related elements. In fact Grid is overkill here, you can span multiple rows or columns using flexbox alone. You don't need to "rebuild" anything, use the same markup.
You couldn't control colspan/rowspan with CSS before, only HTML, which isn't responsive. (Ignoring the `column-span` property which isn't very flexible.)
I'm afraid we are still talking past one another. When I said "rebuild", what i meant was that I have to rebuild the table layout in css using grid to make it responsive. If they added a css equivalent of colspan/rowspan for table-layouts, it would be much easier to do simple tweaks to tables in css. it's not the end of the world that it's not possible, but it's already part of the layout engine so it wouldn't be hard to add. (and the column-span property is unrelated to tables)
The "layout" meaning the visual appearance, or the markup?
There IS a "css equivalent of colspan/rowspan for table-layouts," put another way--a way to make a column or row (<td> or <tr>) appear to span multiple columns or rows. It can be achieved with flexbox or with CSS grid.
You don't need to change anything about the markup (HTML) to make this work.
> to make it responsive
"Responsive" as in "responds to the size of the viewport?" The colspan and rowspan HTML attributes do not do that. They have the same value regardless of the size of the viewport. CSS (with media queries) can respond.
> the column-span property is unrelated to tables
Incorrect, it is certainly related, and works there just fine (along with other in-flow block-level elements).
layout meaning i have to change the display type from table/table-row/table-cell to something else to recreate the way the table is structured. if there was a colspan/rowspan equivalent, i would not have to do this, and would the process would be much simpler. Yes, I know there are workarounds using flexbox, grid, etc. but as I've said before, it would be nice if i didn't have to reconstruct the whole thing in css just becuase there is no rowspan/colspan for table-layouts in css.
there is no equivalent of colspan/rowspan in css, as in not some convoluted workaround but doing exactly the same thing only via css instead of a html attribute. I don't really think I've been that unclear about this being what I want to be added. I was not talking about changing the html, but turning a table into a grid is more than just doing table {display:grid;}, you have to rebuild the whole structure, i.e. layout of the table using css. if i could use rowspan (as in actual literal rowspan not some other workaround), i would not have to do this.
yes responsive as in hide one or multiple rows of table cells on some screen resolutions to make things fit better. as I've previously said.
>Incorrect, it is certainly related, and works there just fine (along with other in-flow block-level elements).
it does not manipulate the amount of table rows a table-cell-display element takes up.
> "responsive" as in hide rows of table cells on some resolutions to make things fit better
HTML attributes (like colspan) don't do this! CSS does.
> it does not manipulate the amount of table rows a table-cell-display element takes up
I know, I mentioned it before :) it's related though.
With HTML attributes the layout is tied to your markup. Like you said it's more than table {display:grid;} but is adding .colspan-2 a lot more cruft than adding [colspan=2]?
i feel like you are being willfully obtuse at this point.
All I want is two new css properties that mirror the behaviour of the colspan/rowspan html tag attributes. for things that are display:table-cell; (i.e. <td> tags). So that you can do light table manipulation without having to use grid or flexbox, which adds extra cruft that would be made redundant in situations where you want to do something very simple like hide a row.
One last time, i understand this is possible with a whole bunch of css to recreate the entire structure of the table using grid, but if we had these two css properties, you would not need to do all this. it would be more convenient.
If you still do not understand what I was trying to say, I'm sorry, I give up. This conversation has started to genuinely frustrate me at this point.
- checkbox/radio inputs can be used to toggle state with `:checked ~ .foo` selectors
- `:focus` (and now `:focus-within`, and `:active` though it’s less useful) can be used similarly, but also allow child selection [Edit to add: `tabindex="-1"` makes anything focusable with a pointer input, but doesn’t capture keyboard tab or iteration with assistive tools]
- `:target` can be used similarly, paired with fragment links [Edit to add: but beware history entries, this can be a poor UX]
- `<label>` can be used to not only set those states but also trigger scrolls (including within `scroll-snap` parents) without creating navigation history entries
- the `attr()` function can be used to reference server-dynamic HTML data for display with `content` in pseudo-elements
- I have to assume CSS animations are adopted widely enough that people aren’t using JS for that where it isn’t needed; but you can also use declarative, even interactive, animations in SVG
- speaking of which, inline SVG (even `<use>` references) are part of the CSS cascade, and you can change any CSS-addressable property with `currentColor`
- and you can nest HTML in SVG with `<foreignObject>` if you want to use SVG techniques in HTML
- probably not worth mentioning but in case you don’t know... if you miss table layouts, you can use them with `display` on basically anything; if you want table semantics without tabular rendering you can override `display` as well
Alllllll of that being said, if you use these techniques check your stuff with assistive technologies!