Conditional CSS
ishadeed.com
ishadeed.com
One more handy feature is data attributes. You can use them to introduce some state to your UI.
Here's a quick and dirty example of a menu for an RPG game.
<ul id="menu" data-can="">
<li id="menu-attack"><button>attack</button></li>
<li id="menu-magic"><button>magic</button></li>
<li id="menu-run"><button>run</button></li>
</ul>
We can use data attribute selectors to hide menu buttons that the player can't use. #menu:not([data-can~=attack]) #menu-attack,
#menu:not([data-can~=magic]) #menu-magic,
#menu:not([data-can~=run]) #menu-run {
display: none;
}
Then all we need to update the menu UI is this single line of JS. document.getElementById("menu").dataset.can = getPossibleActions().join(" ");
You can see a rough example here: https://github.com/kawaiisolutions/tactics. It's very nice for prototyping.But I can't point any functionality that could go through that. Ok, there are a few functionalities that are not independent, but they are still better designed than any alternative I can think.
I'm staying with the opinion that a universal formatting language is inherently complex. And CSS is quite well fit to function.
The only things missing from it having universal logic are coming with the ":has" selector. So there isn't anything to argue about performance. The new ":has" is a performance killer, everybody knows it, nobody really cares because it's only a problem if you abuse it... and of course people will make a point of abusing it.
Interestingly, it is also getting predicate composition, with hierarchical selectors. Looks like the CSS people finally decided to turn it into a usable logic language. The auto generated sites have huge amounts of them because both auto generated code tends to be bad, and because it lacks composition. One of those is being solved.
I'm not familiar with what you're referring to with hierarchical selectors. CSS combinators have always let you select different types of decedents and :has() lets you select either way. Are you referring to Cascade Layers, or maybe something else?
I work on systems software - kernel, daemons, networking, debuggers, machine language disassembly - so I'm used to looking at things which other people say are "hard".
Nothing frustrates me more than writing a Stylus rule or figuring out how to place an element on my Jekyll theme. Every time I do this, my appreciation for modern well-made websites doubles.
CSS is so opaque and impossible, I wouldn't be a web developer for any money.
The problem is most devs, even on the frontend, learn CSS incidentally, not from first principles, so they get annoyed when they can't do stuff. I mean, yeah, if you don't learn something properly of course it won't work as you expect. It's the same as me not understanding Docker until I also learned it from the ground up.
In CSS you can't customize that latter half though, if the 3 things render wonky when they don't fit you just have to hope there is some way to tell CSS to render it the way you want. Sure, you can run a test that says it failed to create the layout you were hoping for... but you already knew that and test or no test your only hope is you can find some way to do it with the predefined options in CSS. And the fun part is sometimes there is no way! You may just have to compromise on how your layout works or push some logic to JS to change the styling so you can do something more akin to the normal code way (at the loss of performance and clarity).
With the inside-out view of HTML then CSS, it feels like that same stance gets muddied because of the technical aspects of the languages and their original intents.
Coding like this removes so much of the flexibility and power of CSS.
The reason we have media queries and now container queries and style queries is because HTML is for structure and CSS is for presentation and the state of things being presented.
If a user selects high contrast mode because they're sight impaired, that's a state issue that has to be handled by CSS; HTML can't help you here:
@media screen and (forced-colors: active) {
…
…
}
To anyone who's learned CSS properly sees this as "IF the user wants forced-colors mode (previously high-contrast mode), THEN execute the selectors between the braces."This is certainly cleaner than an explicit IF/THEN construct for this particular feature.
These really arent decoupled.
Gave up and am doing it like OP now. CSS MUST become a proper programming language (or at least non-turing-complete configuration language like dhall) to solve this.
I assume CSS was made very restrictive on purpose so that it can be applied to the page elements and analyzed very efficiently.
In the worst case just put a 3sec limit on the evaluation and stop it if it doesn't complete in time, problem solved.
That said, I do agree it would've been nice if they had the foresight to use `:has(:checked)` (or some kind of :if() syntax), but it's all hindsight 20/20 and it was the wild west on the web back when a lot of these features were spec'd/implemented. Kinda wish there was a new css version every 10 years or so to fix the inconsistencies (which you would have to opt into).
I think `p:empty { }` is a perfectly clear syntax. As the article says it's already a conditional statement - just imagine an invisible `if` in front of every selector or media query.
CSS is not a procedural language, so I think that if statements could be less clear about how rules are applied.
That reminds me of how I'm supposed to imagine an invisible watashi or an invisible ga in Japanese sentences, which is also incredibly difficult for for non-native speakers to do well (at least, in the beginning, which most do not get far beyond) and causes all sorts of problems for translation engines.
So how about making the language explicit, clear, and hence easier for humans to reason about? All this all you have to do stuff reminds me of how adherents speak about C and Git. Thank God we're seeing viable replacements for C, and I have hope in a replacement for Git (jj or pijul? I pray). If something comes along that is less painful than CSS I'll be the first to jump and I won't be alone.
Let's suppose we have a match function that could be used like this
:root {
--has-path: match(.modal .message .error);
}and then if the following were allowed (or some variation were developed where this could be done)
.user:has(var(--has-path)) {color: red}
I think it might be nice. YMMV however.
on edit: thinking about it however while it might be nice if you could do queries like that, it does seem like it would be expensive (for browser performance) to implement.
The other issue is that custom properties inherit, so now it's unclear what element you should fetch your var() from (you can't fetch it from the element you're styling, since you haven't computed its style yet). E.g.
.user:has(var(--has-path)) { --has-path: match(.something .else); }
which is why I specifically said it wouldn't work because of performance issues? This part here at the end >it does seem like it would be expensive (for browser performance) to implement.
>The other issue is that custom properties inherit
I'm not sure I see the problem with that actually -
If I set a value on :root and then override it on .foo then I thought it only got overridden for the descendants of .foo?
on edit: ah no of course you mean that if I set --base-color on .foo it is also available for .foo, but in that case I would think the rules would mean that use in selector was outside the scope of the element, that is to say scoped to the parent.
I think this is a mistake in system design. CSS doesn't know or care or work with what a user is, what a foo is, what a bar is or how any of those interact conceptually. CSS operates on the rendered output of decisions made at that level: it shouldn't make those decisions, nor encode knowledge of those decisions. That's what JS is for, and what it's better at.
I think it makes much more sense architecturally to have JS respond to whatever condition triggered the error state by updating some metadata on everything involved (User{}, Booking{}, Etc{}).
```
// pseudo JS uiEvents.subscribe('error message', () => {
appState.User.hasBadData = true;
})uiEvents.subscribe('error message', () => {
appState.Booking.hasBadData = true;
})function validateForm(fields) {
fields.forEach(field => {
if(field.hasError) {
uiEvents.publish({some: "error message"});
}
}
}// pseudo React/Vue/whatever in pseudo TypeScript
render(({user, booking}: TAppState) => (
<>
<div class={`user ${user.hasBadData? 'user--error' : ''}`}>/\* user icons and text */</div>
<div class={`booking ${booking.hasBadData? 'booking--error' : ''}`}>
/* booking stuff \*
</div>
<>
});```
You could replace all the JS with server-side logic (which would probably take it closer to the "single source of truth" of users and booking anyway). Further, maybe you're making application with multiple communication modalities, and you want to play a sound or trigger speech when the user enters bad data, instead/as well as colouring everything red. CSS can't help you there. It's much better to keep the logic, state and behaviours out of the CSS and use it only to "dress up" the rendered markup, otherwise you make maintenance harder.
[1]: "The - -var: ; hack to toggle multiple values with one custom property"—https://lea.verou.me/2020/10/the-var-space-hack-to-toggle-mu...
I'm not sure I totally understand the specifics of the example, but it's probably possible to get something close with the nested selectors spec being worked on now, and/or :is()
I figured it was implied some rules would have to be made which might be properties used in selectors are scoped to ancestors of element.
>I'm not sure I totally understand the specifics of the example
example is probably too fanciful but assuming you have a match of selector
.modal .message .error
then element with class user which is on a totally different part of the page - like
body
header
....stuff .user
/headerarticle
section
.modal .warning .error
then user has color red. Do I personally want this, not much, just considering the people who sometimes say they want if ( selector exists) then bunch of css.
another more likely usage for something like this (a match function that takes a selector) might be the various CSS logical operations things people do sometimes https://css-tricks.com/logical-operations-with-css-variables...
however, as I noted before I don't think it makes sense for css performance reasons, despite how interesting/amusing I might find the idea.
I quickly found and then ran into issues with aspect-ratio. Either the canvas could get smooshed at certain sizes if I set width: 100vw and max-height: 100vh (despite setting the aspect-ratio) or it wouldn't do anything but be the default 300x150 canvas if not specified. There may well be a way to fix this but I got bored and just as I was switching to write the JS I had couldn't help but think "this feels doable with calc() and min()/max()". So I popped open an Excel sheet, created some random widths and heights, and started messing around for formulas that worked but didn't rely on variables or explicit conditionals. What I came to was:
--Canvas-Width: min(100vw, calc(100vh * 16 / 9));
--Canvas-Height: min(100vh, calc(100vw * 9 / 16));
Which is basically using min() to be the "if we are vertically/horizontally constrained" conditional. At this point since I had the height and knew it was of 100vh I also had a lazy way out on vertically centering with another calc().It was definitely fun to tinker to a solution but if anyone actually knows CSS and how to make the built in aspect-ratio work the same way on canvas I wouldn't mind hearing it :).
I had to once do something similar and I ran into 4 cases (which can be collapsed into 2 cases): add vertical borders if the window is very wide or a little narrow. Add horizontal borders if the window is a little wide or very narrow.
Try something like this: https://jsfiddle.net/pgaz7cmx/, which sets both, width and max-width.
I’m sure there are other ways to achieve the same, some are perhaps more elegant?
Should I group by behaviour type — put all the margins and padding in one place, all the font stuff in another? How does that jive with grouping in a different dimension: by element hierarchy. All the buttons stuff in one file. All the headings stuff in another.
This feels a very basic question so I hope someone will take pity on me and answer the question I was trying to ask.
> I published a book about debugging CSS
…and it looks amazing. From the sample chapters it’s clear the author knows their stuff. I wonder if they have considered creating a Python (or Clippy!) style linter for CSS that automatically offers advice?
I've been where you are.
All I can say is Inverted Triangle CSS (ITCSS) [1] solves the issues you're talking about. It's so obvious once you get it—it's one of those "why didn't I think of this" kind of thing. You write your code in specificity order. It sounds odd at first but it makes total sense and eliminates so many of the "issues" people complain about. It makes the cascade your friend and not your foe.
[1]: https://blog.codeminer42.com/how-to-organize-your-styles-wit...
But a number of things are still Chrome-only, and even with that, only available in the latest version. Will have to wait before I can apply them.
Firefox is working on shipping :has() and Container Queries.
Safari supports everything besides style queries. Many of these, Safari shipped first. More importantly, these days, browsers often ship new CSS around the same time.
Plus you can always use Feature Queries (@supports) to conditionally test for support and apply different CSS depending on what's supported. That way users who haven't updated their software in years have a great experience, too.
So no, you don't have to wait to use this CSS. Just think through what you are doing. Learn how here: https://www.youtube.com/playlist?list=PLbSquHt1VCf1kpv9WRGMC...
I was able to make some complex hover menus and sidebar toggles work like this entirely without JavaScript. It's also accessible, to the user. The downside is that for the developer, the resulting code is not easy to read nor maintain.
I would link to it but it's in a private repository atm. One day I'll open source this behavior as its own package... one day...
Stick to using a button with an aria-expanded state to toggle. You can make the sibling menu contents visible based on that state.
button.menu[aria-expanded="false"] + .menu-list { display: none; } button.menu[aria-expanded="true"] + .menu-list { display: block; }
the relevant style applied looks like this:
.modal-open, .modal:target, .modal-toggle:checked+.modal {
pointer-events: auto;
visibility: visible;
opacity: 1;
}