HTML and CSS techniques to reduce your JavaScript
calendar.perfplanet.com
calendar.perfplanet.com
- 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!
- you want to at least test with JAWS, NVDA and VoiceOver
- automated tooling isn’t great and you probably need to manually verify things
- axe does help spot problems just don’t rely on it completely
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.
[0] https://webaim.org/projects/screenreadersurvey8/ [1] https://a11ysupport.io/
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.
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.
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.
except not really because there's no CSS equivalent for rowspan/colspan. wish they would add this, it'd also be useful for making tables responsive.
Thanks for the attr tip though, somehow that one had completely slipped by me!
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_La...
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 have to rebuild the table layout
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).
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.
Yes! What's wrong with this approach? We have a lot of old HTML attributes that were used for styling that have been replaced by CSS.
> Yes, I know there are workarounds using flexbox, grid, etc
It's not a "workaround!" You're controlling the appearance using the styling language. What's wrong with this example?
> "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]?
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.
What kind of state do you mean? Could you give an example?
<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.
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.
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.
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.
<label>
<input type="checkbox">
<span class="label">Some label</span>
</label>
input[type="checkbox"] {
appearance: none;
/* ... */
}
input[type="checkbox"]:checked { /* ... */ }
input[type="checkbox"]:indeterminate { /* ... */ }
input[type="checkbox"]:disabled { /* ... */ }There are still more things you can do within a label. This is a good article on improving methods of visually hiding the actual inputs.
https://www.sarasoueidan.com/blog/inclusively-hiding-and-sty...
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.
<form id="foo"></form>
<div class="target">...</div>
<div class="doesnt-matter">
<input type="checkbox" form="foo" required>
</div>
You can target `.target` with: #foo:invalid ~ .target
Because the checkbox is required/unchecked. (And the inverse if it’s checked using `:valid`.)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:
https://codepen.io/inopinatus/pen/vYxrOeR?editors=1100
There are two downsides:
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?"
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.
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.
Is there any good way to see why an element is a certain size? I often end up digging back and forth through children and parents and wanting to tear my hair out.
Given the properties, I personally find sizes to be pretty predictable based on the rules of block or flex sizing
Generally, nearly every element's size comes down to some combination of:
1) Explicit width/height properties (and padding/margin)
2) Special layout mechanisms like filebox or grid
3) The element's contents
4) Its direct parent's dimensions
That's pretty much it. There's really no case I can think of where a grandparent directly drives an element's size (it may drive the parent's size which drives the child's size, but the reach at each individual stage remains short)
But I can still go with this:
I inspect the tab toolbar. I pick a tab and look at the contents of the div. Just inside the actual tab div is a "stack" element, whose width is being set to the size of its contents right now.
The stack is size 50.5x44, and it's display:grid.
It has two visible children. Both of them are 50.5x44, and are display: -moz-box.
I can tell from deep inspection and trial and error that one of those children is setting the size, and the other child is inheriting the size.
But if I don't want to dig around every single sub-div, what can I do to figure out which one is which?
They look the same in the layout inspector, and neither of them has any computed properties relative to width.
For bonus difficulty: If the parent div of the "stack" element tries to grow wider than the stack, the stack will grow to match its width, and the child divs of the stack will also grow to match it. All without any computed properties changing, or the layout inspector noting that anything is different.
So the size of the "stack" element could be set by either its parent or one of its children and I have no idea how to figure out which one except by experimentation. This is not a very pleasant experience.
Ideally, I could get a list of every element competing to set the width of a div, and which one(s) are winning. Less ideally, I could see just which element(s) are currently responsible for the size of a div. But as far as I can tell there's nothing, and I have to blindly search every other related element that has a suspiciously similar size.
Element sizing in CSS is anything but straight forward, especially as there are loads of non obvious rules applied to elements like text inputs etc
But maybe I am just not as good at CSS as I thought :)
it extends the expressive power of HTML as a hypertext and allows you to do quite a bit more within the hypertext paradigm.
Thank you!
HTMX seems as though it will catalyze those efforts.
HTMX + TailwindCSS, so almost everything client-side is in HTML. And I use FreeMarker macros to generate the HTML components.
The gallery with horizontal snap scrolling, the use of smooth scrolling to jump between anchors and even clamp are all quite cool.
I also had no idea that's how sticky worked!
The biggest issue that happens to me is that I'll have a large data table where I want `overflow-x:auto` with a sticky header, and this is just impossible without Javascript. Of course, the UX of such a table is not ideal, but sometimes nothing will substitute for a large data table when you have a lot of data that goes into a table and people want to see it.
Edit oh I missed the "x" there, I see.
While I agree there ARE some industries that have to support stupid old versions (eg government websites), any other product managers that think they'll be losing business by not supporting IE are misguided.
And the school site has quite an active userbase - students upload assignments via it, parents can live-view all gradings and remarks (and regularly use it), and staff generally access their email via it instead of a client.
[1] https://techcommunity.microsoft.com/t5/windows-it-pro-blog/i...
[2] https://docs.microsoft.com/en-us/lifecycle/announcements/int...
Sure, which can be countered with: "no problem, it will add 30% to the estimate".
Unless the % is significant (over 10%), it's not worth the effort.
One day Safari will enable smooth scroll.
Also don’t forget to check actual conversion and other important metrics for different browsers, not only number of visits.
https://techcommunity.microsoft.com/t5/windows-it-pro-blog/i...
The problem isn't just IE marketshare. Not every web developer works in e-commerce, on brochure sites or even SaaS apps with a target audience consisting almost entirely of other startups. If you build products for mostly enterprise customers, those customers often dictate what you support, even if they barely even use it anymore. Changing requirements can be difficult.
I remember being asked by a large international company to build a web app in "HTML5 but it must work with IE6" back in the day. Most of their actual users were on Firefox or using iPads.
If you want a modern UX, use a modern browser.
That being said, in some places IE is still enforced and you have no choice. I used to work at a place that enforced IE6 for the longest time, that was really awful.
This issue had been open for years. IDK if the previous developer had a look at it, but the interim guy - some PHP consultant - spent a few days trying to fix it.
I added `position:sticky` to it and fixed it in minutes, everywhere. Not to boast or anything, but that's all it took. Okay fine I am boasting that my google-fu was slightly better than the other guys.
Didn't know about the `loading=lazy` attribute on images yet, mentally I was still thinking of plugins and JS libraries that take care of it for you.
Weird notion:
Is there a FindBugs for HTML, CSS, JavaScript?
Such a tool might have to analyze the live DOM, with the actual behavior, to infer what's actually happening, to better suggest refactorings.
Amazon.com works great with NO javascript. Seamlessly.
I'm not sure if it's completely JS-free but Gmail also has a Basic HTML view, although you must enable JS in order to login to your Google Account.
However, this seems like a weird comment. Should it be surprising that some of the world's largest tech companies with effectively infinite resources are able to provide a JS-free version of certain services?
I am grateful for the gmail html version though. ever since they redesigned gmail I've been using that, since it still has the old interface. and it does have optional javascript based enhancements here and there, as you say (e.g. suggesting people from your contacts when typing in a recipient)
JavaScript can load just as fast as CSS etc if done carefully. If you really want to, you can put some JS right in the HTML file, immediately following the elements it is going to operate upon, changing the display of the elements before they are even rendered to the screen. Users will experience no delay (or flashing) at all. Whether or not that is considered good practice is another matter, but it most certainly is possible to eliminate the delay that is discussed in the article without abandoning JavaScript approaches.
The problem is when people require a big framework be loaded just to do simple things. Or, I guess they write JavaScript that is so complicated that it causes a noticeable delay simply due to loading the massive code. But that is an awful lot of code. The examples given would never need more than tiny amount of JS code.
There are other reasons, of course (people who turn off JS still exist, I'm told), but as someone who simply knows JS well but doesn't know nearly as much about CSS (which has far too many surprises and special cases for my liking), I tend to be of the "if all you've got is a hammer, everything looks like a nail" mindset. So I'm unlikely to figure out how to do something in HTML/CSS if I can do it instantly in JavaScript. (obviously I do regular styling in CSS.... I'm talking about the more advanced stuff as per the article) That's just my reality, right or wrong.
This is indeed a cool trick I have seen in use in the wild. Can it work with CSP, however?
It can work, if you specifically add the hash of that bit of JS to script-src or use the nonce based approach (I never figured that one out).
Otherwise it's unsafe-inline, at which point you may aswell not bother ;)
div {
behavior:check;
}
div:checked {
color:red;
}
and all divs will behave as checkboxes - will toggle :checked flag on clicks.Same way:
div {
behavior:radio;
}
div:checked {
color:red;
}
Or even : table > tbody { /* scrollable table body, behaves as <select>*/
behavior:select;
overflow-y: auto;
}
table > tbody > tr:current { /*current "option" */
color:red;
}
Also you can put this in your markup: <frameset cols="120px,*">
<div>left</div>
<div>right</div>
</frameset>
to have split view.No script is required for all these.
`document.getElementById("id")` can be replaced by `window["id"]`.
So no, its not ironic.
* Why may? The older APIs such as document.write and synchronous XHR do but modern browsers already warn against that. And the modern APIs don't block the UI because they are asynchronous and work with callbacks or promises. Bad JavaScript code can make the UI sluggish though, people should be performing complex tasks on a WebWorker but that's not as easy or as obvious as the default of performing them on the UI thread. The same problems can happen in native Windows programming for the same reason, the UI thread being the main thread. This is a bad design decision from decades ago that will likely haunt us for many more decades in the desktop OS and on the web.
This doesn't make Javascript multithreaded. It means you run single-threaded programs in separate containers and pass messages between them.
People who actually care about multithreading
> you can create multiple workers, running on different threads. That’s multithreading.
It's not.
=== start quote ===
To share memory using SharedArrayBuffer objects from one agent in the cluster to another (an agent is either the web page’s main program or one of its web workers), postMessage and structured cloning is used.
=== end quote ===
And, of course, they are almost exclusively used with Web Workers because it makes zero sense to use them in the context of a single page. They are basically memory-mapped files, but for the browser, and their presence doesn't make Javascript and its runtime multithreaded.
There's also a good overview on the history of concurrency and parallelism in JS here: https://exploringjs.com/es2016-es2017/ch_shared-array-buffer...
IIRC Javascript can't even be made multithreaded because there are places in the spec that can't work in a multithreaded environment, but don't quote me on that.
> However, the shared data block referenced by the two SharedArrayBuffer objects is the same data block, and a side effect to the block in one agent will eventually become visible in the other agent.
This gives you shared memory between two threads. Sure the entire address space isn't shared but I find it hard to deny that this is threading.
This is the key point you're missing. It's not "two threads". It's to different isolated tasks/processes.
Shared array buffers are quite literally what memory-mapped files have been used for over 40 years [1]
=== start quote ===
Another common use for memory-mapped files is to share memory between multiple processes. In modern protected mode operating systems, processes are generally not permitted to access memory space that is allocated for use by another process... There are a number of techniques available to safely share memory, and memory-mapped file I/O is one of the most popular. Two or more applications can simultaneously map a single physical file into memory and access this memory.
=== end quote ===
That's all there is: two separate, isolated processes accessing the same memory. This doesn't make Javascript multithreaded in any way, shape, or form. Browsers give you a rather awkward way to run a separate tasks in Javascript and give you message-passing and shared memory as a way to communicate between them.
Had browsers been able to run other languages than Javascript, you would be able to run one worker in Javascript, and another in SadlyNonExistentScript, and literally nothing would change: you would still have the same postMessage and SharedArrayBuffer as APIs provided by the host.
These are the browser's background threads, maybe. They don't make Javascript multithreaded or even Javascript runtimes multithreaded.
It's not even green threads.
Also, literally on the page you linked:
=== start quote ===
The Worker interface spawns real OS-level threads,
=== end quote ===
[1]
> it doesn't spawn an OS thread but it spawns lightweight (or green or simulated) threads with their own VM.
It spawns an isolated process. Javascript as a language and its runtime cannot support threads. To do "threads" they basically initialise a new instance of JS runtime.
This is not "threading" by any definition. MDN page may call that for the sake of people who end up using it, but these are not:
- threads
- green threads
- lightweight
If in doubt, you could try and find any implementation of a thread or a worker in the JS VM: https://github.com/mozilla/gecko-dev/tree/master/js/src/vm
It's not there, because workers are implemented at the DOM level, in the browser: https://github.com/mozilla/gecko-dev/tree/master/dom/workers
It's an outside implementation, running processes inside the host, and letting processes communicate using memory-mapped values. 20 years ago no one in their right mind would call this "multithreading in language X". It was "app 1 written in any language is communicating with app 2 written in any language via memory-mapped files". Now people who've never seen anything outside web development call it multithreading.
[1] Fun trivia: original implementation literally used a runtime per worker: https://blog.mozilla.org/luke/2012/01/24/jsruntime-is-now-of... It still uses a CycleCollectedJSRuntime per worker, but I'm too lazy to dig through source code for further details.
Sometimes I need an answer as blunt and direct as that one to understand something, thanks.
http://lambdaway.free.fr/lambdawalks/?view=scroll_snap
To be improved for non squared pictures