#Article:
Explore tagged Tumblr posts
Text
Solved by CSS: Donuts Scopes
New Post has been published on https://thedigitalinsider.com/solved-by-css-donuts-scopes/
Solved by CSS: Donuts Scopes
Imagine you have a web component that can show lots of different content. It will likely have a slot somewhere where other components can be injected. The parent component also has its own styles unrelated to the styles of the content components it may hold.
This makes a challenging situation: how can we prevent the parent component styles from leaking inwards?
This isn’t a new problem — Nicole Sullivan described it way back in 2011! The main problem is writing CSS so that it doesn’t affect the content, and she accurately coined it as donut scoping.
“We need a way of saying, not only where scope starts, but where it ends. Thus, the scope donut”.
Even if donut scoping is an ancient issue in web years, if you do a quick search on “CSS Donut Scope” in your search engine of choice, you may notice two things:
Most of them talk about the still recent @scope at-rule.
Almost every result is from 2021 onwards.
We get similar results even with a clever “CSS Donut Scope –@scope” query, and going year by year doesn’t seem to bring anything new to the donut scope table. It seems like donut scopes stayed at the back of our minds as just another headache of the ol’ CSS global scope until @scope.
And (spoiler!), while the @scope at-rule brings an easier path for donut scoping, I feel there must have been more attempted solutions over the years. We will venture through each of them, making a final stop at today’s solution, @scope. It’s a nice exercise in CSS history!
Take, for example, the following game screen. We have a .parent element with a tab set and a .content slot, in which an .inventory component is injected. If we change the .parent color, then so does the color inside .content.
How can we stop this from happening? I want to prevent the text inside of .content from inheriting the .parent‘s color.
Just ignore it!
The first solution is no solution at all! This may be the most-used approach since most developers can live their lives without the joys of donut scoping (crazy, right?). Let’s be more tangible here, it isn’t just blatantly ignoring it, but rather accepting CSS’s global scope and writing styles with that in mind. Back to our first example, we assume we can’t stop the parent’s styles from leaking inwards to the content component, so we write our parent’s styles with less specificity, so they can be overridden by the content styles.
body color: blue; .parent color: orange; /* Initial background */ .content color: blue; /* Overrides parent's background */
While this approach is sufficient for now, managing styles just by their specificity as a project grows larger becomes tedious, at best, and chaotic at worst. Components may behave differently depending on where they are slotted and changing our CSS or HTML can break other styles in unexpected ways.
Two CSS properties walk into a bar. A barstool in a completely different bar falls over.
Thomas Fuchs
You can see how in this small example we have to override the styles twice:
Shallow donuts scopes with :not()
Our goal then it’s to only scope the .parent, leaving out whatever may be inserted into the .content slot. So, not the .content but the rest of .parent… not the .content… :not()! We can use the :not() selector to scope only the direct descendants of .parent that aren’t .content.
body color: blue; .parent > :not(.content) color: orange;
This way the .content styles won’t be bothered by the styles defined in their .parent:
You can see an immense difference when we open the DevTools for each example:
As good as an improvement, the last example has a shallow reach. So, if there were another slot nested deeper in, we wouldn’t be able to reach it unless we know beforehand where it is going to be slotted.
This is because we are using the direct descendant selector (>), but I couldn’t find a way to make it work without it. Even using a combination of complex selectors inside :not() doesn’t seem to lead anywhere useful. For example, back in 2021, Dr. Lea Verou mentioned donut scoping with :not() using the following selector cocktail:
.container:not(.content *) /* Donut Scoped styles (?) */
However, this snippet appears to match the .container/.parent class instead of its descendants, and it’s noted that it still would be shallow donut scoping:
TIL that all modern browsers now support complex selectors in :not()! 😍
Test: https://t.co/rHSJARDvSW
So you can do things like: – .foo :not(.foo .foo *) to match things inside one .foo wrapper but not two – .container :not(.content *) to get simple (shallow) “donut scope”
— Dr Lea Verou (@LeaVerou) January 28, 2021
Donut scoping with @scope
So our last step for donut scoping completion is being able to go beyond one DOM layer. Luckily, last year we were gifted the @scope at-rule (you can read more about it in its Almanac entry). In a nutshell, it lets us select a subtree in the DOM where our styles will be scoped, so no more global scope!
@scope (.parent) /* Styles written here will only affect .parent */
What’s better, we can leave slots inside the subtree we selected (usually called the scope root). In this case, we would want to style the .parent element without scoping .content:
@scope (.parent) to (.content) /* Styles written here will only affect .parent but skip .content*/
And what’s better, it detects every .content element inside .parent, no matter how nested it may be. So we don’t need to worry about where we are writing our slots. In the last example, we could instead write the following style to change the text color of the element in .parent without touching .content:
body color: blue; @scope (.parent) to (.content) h2, p, span, a color: orange;
While it may seem inconvenient to list all the elements we are going to change, we can’t use something like the universal selector (*) since it would mess up the scoping of nested slots. In this example, it would leave the nested .content out of scope, but not its container. Since the color property inherits, the nested .content would change colors regardless!
And voilà! Both .content slots are inside our scoped donut holes:
Shallow scoping is still possible with this method, we would just have to rewrite our slot selector so that only direct .content descendants of .parent are left out of the scope. However, we have to use the :scope selector, which refers back to the scoping root, or .parent in this case:
@scope (.parent) to (:scope > .content) * color: orange;
We can use the universal selector in this instance since it’s shallow scoping.
Conclusion
Donut scoping, a wannabe feature coined back in 2011 has finally been brought to life in the year 2024. It’s still baffling how it appeared to sit in the back of our minds until recently, as just another consequence of CSS Global Scope, while it had so many quirks by itself. It would be unfair, however, to say that it went under everyone’s radars since the CSSWG (the people behind writing the spec for new CSS features) clearly had the intention to address it when writing the spec for the @scope at-rule.
Whatever it may be, I am grateful we can have true donut scoping in our CSS. To some degree, we still have to wait for Firefox to support it. 😉
This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.
Desktop
Chrome Firefox IE Edge Safari 118 No No 118 17.4
Mobile / Tablet
Android Chrome Android Firefox Android iOS Safari 131 No 131 17.4
#:not#2024#approach#Articles#at-rules#background#Blue#browser#challenge#change#Color#colors#container#content#CSS#csswg#data#developers#DevTools#engine#Exercise#Features#firefox#game#Global#global scope#History#how#HTML#it
0 notes
Photo
How Concerned Citizens Drove a Neo-Nazi Out of Rural Maine
Christopher Pohlhaus planned to build a fascist training compound in the woods of rural Maine. The local journalists, veterans, lumberjacks, and policymakers weren't having it.
Pohlhaus, 37, is a former U.S. marine, an itinerant tattoo artist, and a hardcore white-supremacist influencer. He is loud and hostile, and proud to be both. His voice is pitched surprisingly high, and he has a slight Southern drawl. He has a large body and small bald head; a blue-black tattoo crawls up the right side of his face, from his chin to his forehead. Over the years, Pohlhaus has collected thousands of social media followers, who know him by his nickname: Hammer.
Hammer had been living in Texas for a few years when, in March 2022, he bought the land in Maine. He told his followers that he was going to use it to build a haven, operational center, and training ground for white supremacists.
Check out our excerpt of The Atavist’s latest blockbuster story.
1K notes
·
View notes
Photo
PORTO ROCHA
852 notes
·
View notes
Photo
🧀🥪🌶️🥭 The Ravening War portraits 🧀🥪🌶️🥭
patreon * twitch * shop
[ID: a series of digitally illustrated portraits showing - top left to bottom right - Bishop Raphaniel Charlock (an old radish man with a big red head and large white eyebrows & a scraggly beard. he wears green and gold robes with symbols of the bulb and he smirks at the viewer) Karna Solara (a skinny young chili pepper woman with wavy green hair, freckled light green skin with red blooms on her cheeks. she wears a chili pepper hood lined with small pepper seeds and stares cagily ahead) Thane Delissandro Katzon (a muscular young beef man with bright pinkish skin with small skin variations to resemble pastrami and dark burgundy hair. he wears a bread headress with a swirl of rye covering his ears and he looks ahead, optimistic and determined) Queen Amangeaux Epicée du Peche (a bright mango woman with orange skin, big red hair adorned with a green laurel, and sparkling green/gold makeup. she wears large gold hoop earrings and a high leafy collar) and Colin Provolone (a scraggly cheese man with waxy yellow skin and dark slicked back hair and patchy dark facial hair. he wears a muted, ratty blue bandana around his neck and raises a scarred brow at the viewer with a smirk) End ID.)
#trw#the ravening war#dimension 20#acoc#trw fanart#ttrpg#dnd#bishop raphaniel charlock#karna solara#thane delissandro katzon#queen amangeaux epicee du peche#colin provolone
2K notes
·
View notes
Photo
One of my favorites by Paul Lehr, used as a 1971 cover to "Earth Abides," by George R. Stewart. It's also in my upcoming art book!
1K notes
·
View notes
Quote
もともとは10年ほど前にTumblrにすごくハマっていて。いろんな人をフォローしたらかっこいい写真や色が洪水のように出てきて、もう自分で絵を描かなくて良いじゃん、ってなったんです。それで何年も画像を集めていって、そこで集まった色のイメージやモチーフ、レンズの距離感など画面構成を抽象化して、いまの感覚にアウトプットしています。画像の持つ情報量というものが作品の影響になっていますね。
映画『きみの色』山田尚子監督×はくいきしろい対談。嫉妬し合うふたりが語る、色と光の表現|Tokyo Art Beat
147 notes
·
View notes
Photo
#thistension
XO, KITTY — 1.09 “SNAFU”
#xokittyedit#tatbilbedit#kdramaedit#netflixedit#wlwedit#xokittydaily#asiancentral#cinemapix#cinematv#filmtvcentral#pocfiction#smallscreensource#teendramaedit#wlwgif#kitty song covey#yuri han#xo kitty#anna cathcart#gia kim#~#inspiration: romantic.#dynamic: ff.
1K notes
·
View notes
Photo
No one wants to be here and no one wants to leave, Dave Smith (because)
100 notes
·
View notes
Photo
Noodles with Lamb Sauce (Laghman, 新疆拌面) Xinjiang laghman features chewy noodles served with a bold and rich lamb and tomato sauce that is bursting with flavor.
Recipe: https://omnivorescookbook.com/recipes/uyghur-style-noodles-with-lamb-sauce
121 notes
·
View notes
Photo
CJ from Hello World (MSPFA) by phasedsun?
111 notes
·
View notes
Text
Invoker Commands: Additional Ways to Work With Dialog, Popover… and More?
New Post has been published on https://thedigitalinsider.com/invoker-commands-additional-ways-to-work-with-dialog-popover-and-more/
Invoker Commands: Additional Ways to Work With Dialog, Popover… and More?
The Popover API and <dialog> element are two of my favorite new platform features. In fact, I recently [wrote a detailed overview of their use cases] and the sorts of things you can do with them, even learning a few tricks in the process that I couldn’t find documented anywhere else.
I’ll admit that one thing that I really dislike about popovers and dialogs is that they could’ve easily been combined into a single API. They cover different use cases (notably, dialogs are typically modal) but are quite similar in practice, and yet their implementations are different.
Well, web browsers are now experimenting with two HTML attributes — technically, they’re called “invoker commands” — that are designed to invoke popovers, dialogs, and further down the line, all kinds of actions without writing JavaScript. Although, if you do reach for JavaScript, the new attributes — command and commandfor — come with some new events that we can listen for.
Invoker commands? I’m sure you have questions, so let’s dive in.
We’re in experimental territory
Before we get into the weeds, we’re dealing with experimental features. To use invoker commands today in November 2024 you’ll need Chrome Canary 134+ with the enable-experimental-web-platform-features flag set to Enabled, Firefox Nightly 135+ with the dom.element.invokers.enabled flag set to true, or Safari Technology Preview with the InvokerAttributesEnabled flag set to true.
I’m optimistic we’ll get baseline coverage for command and commandfor in due time considering how nicely they abstract the kind of work that currently takes a hefty amount of scripting.
Basic command and commandfor usage
First, you’ll need a <button> or a button-esque <input> along the lines of <input type="button"> or <input type="reset">. Next, tack on the command attribute. The command value should be the command name that you want the button to invoke (e.g., show-modal). After that, drop the commandfor attribute in there referencing the dialog or popover you’re targeting by its id.
<button command="show-modal" commandfor="dialogA">Show dialogA</button> <dialog id="dialogA">...</dialog>
In this example, I have a <button> element with a command attribute set to show-modal and a commandfor attribute set to dialogA, which matches the id of a <dialog> element we’re targeting:
Let’s get into the possible values for these invoker commands and dissect what they’re doing.
Looking closer at the attribute values
The show-modal value is the command that I just showed you in that last example. Specifically, it’s the HTML-invoked equivalent of JavaScript’s showModal() method.
The main benefit is that show-modal enables us to, well… show a modal without reaching directly for JavaScript. Yes, this is almost identical to how HTML-invoked popovers already work with thepopovertarget and popovertargetaction attributes, so it’s cool that the “balance is being redressed” as the Open UI explainer describes it, even more so because you can use the command and commandfor invoker commands for popovers too.
There isn’t a show command to invoke show() for creating non-modal dialogs. I’ve mentioned before that non-modal dialogs are redundant now that we have the Popover API, especially since popovers have ::backdrops and other dialog-like features. My bold prediction is that non-modal dialogs will be quietly phased out over time.
The close command is the HTML-invoked equivalent of JavaScript’s close() method used for closing the dialog. You probably could have guessed that based on the name alone!
<dialog id="dialogA"> <!-- Close #dialogA --> <button command="close" commandfor="dialogA">Close dialogA</button> </dialog>
The show-popover, hide-popover, and toggle-popover values
<button command="show-popover" commandfor="id">
…invokes showPopover(), and is the same thing as:
<button popovertargetaction="show" popovertarget="id">
Similarly:
<button command="hide-popover" commandfor="id">
…invokes hidePopover(), and is the same thing as:
<button popovertargetaction="hide" popovertarget="id">
Finally:
<button command="toggle-popover" commandfor="id">
…invokes togglePopover(), and is the same thing as:
<button popovertargetaction="toggle" popovertarget="id"> <!-- or <button popovertarget="id">, since ‘toggle’ is the default action anyway. -->
I know all of this can be tough to organize in your mind’s eye, so perhaps a table will help tie things together:
command Invokes popovertargetaction equivalent show-popover showPopover() show hide-popover hidePopover() hide toggle-popover togglePopover() toggle
So… yeah, popovers can already be invoked using HTML attributes, making command and commandfor not all that useful in this context. But like I said, invoker commands also come with some useful JavaScript stuff, so let’s dive into all of that.
Listening to commands with JavaScript
Invoker commands dispatch a command event to the target whenever their source button is clicked on, which we can listen for and work with in JavaScript. This isn’t required for a <dialog> element’s close event, or a popover attribute’s toggle or beforetoggle event, because we can already listen for those, right?
For example, the Dialog API doesn’t dispatch an event when a <dialog> is shown. So, let’s use invoker commands to listen for the command event instead, and then read event.command to take the appropriate action.
// Select all dialogs const dialogs = document.querySelectorAll("dialog"); // Loop all dialogs dialogs.forEach(dialog => // Listen for close (as normal) dialog.addEventListener("close", () => // Dialog was closed ); // Listen for command dialog.addEventListener("command", event => // If command is show-modal if (event.command == "show-modal") // Dialog was shown (modally) // Another way to listen for close else if (event.command == "close") // Dialog was closed ); );
So invoker commands give us additional ways to work with dialogs and popovers, and in some scenarios, they’ll be less verbose. In other scenarios though, they’ll be more verbose. Your approach should depend on what you need your dialogs and popovers to do.
For the sake of completeness, here’s an example for popovers, even though it’s largely the same:
// Select all popovers const popovers = document.querySelectorAll("[popover]"); // Loop all popovers popovers.forEach(popover => // Listen for command popover.addEventListener("command", event => // If command is show-popover if (event.command == "show-popover") // Popover was shown // If command is hide-popover else if (event.command == "hide-popover") // Popover was hidden // If command is toggle-popover else if (event.command == "toggle-popover") // Popover was toggled ); );
Being able to listen for show-popover and hide-popover is useful as we otherwise have to write a sort of “if opened, do this, else do that” logic from within a toggle or beforetoggle event listener or toggle-popover conditional. But <dialog> elements? Yeah, those benefit more from the command and commandfor attributes than they do from this command JavaScript event.
Another thing that’s available to us via JavaScript is event.source, which is the button that invokes the popover or <dialog>:
if (event.command == "toggle-popover") // Toggle the invoker’s class event.source.classList.toggle("active");
You can also set the command and commandfor attributes using JavaScript:
const button = document.querySelector("button"); const dialog = document.querySelector("dialog"); button.command = "show-modal"; button.commandForElement = dialog; /* Not dialog.id */
…which is only slightly less verbose than:
button.command = "show-modal"; button.setAttribute("commandfor", dialog.id);
Creating custom commands
The command attribute also accepts custom commands prefixed with two dashes (--). I suppose this makes them like CSS custom properties but for JavaScript events and event handler HTML attributes. The latter observation is maybe a bit (or definitely a lot) controversial since using event handler HTML attributes is considered bad practice. But let’s take a look at that anyway, shall we?
Custom commands look like this:
<button command="--spin-me-a-bit" commandfor="record">Spin me a bit</button> <button command="--spin-me-a-lot" commandfor="record">Spin me a lot</button> <button command="--spin-me-right-round" commandfor="record">Spin me right round</button>
const record = document.querySelector("#record"); record.addEventListener("command", event => if (event.command == "--spin-me-a-bit") record.style.rotate = "90deg"; else if (event.command == "--spin-me-a-lot") record.style.rotate = "180deg"; else if (event.command == "--spin-me-right-round") record.style.rotate = "360deg"; );
event.command must match the string with the dashed (--) prefix.
Are popover and <dialog> the only features that support invoker commands?
According to Open UI, invokers targeting additional elements such as <details> were deferred from the initial release. I think this is because HTML-invoked dialogs and an API that unifies dialogs and popovers is a must-have, whereas other commands (even custom commands) feel more like a nice-to-have deal.
However, based on experimentation (I couldn’t help myself!) web browsers have actually implemented additional invokers to varying degrees. For example, <details> commands work as expected whereas <select> commands match event.command (e.g., show-picker) but fail to actually invoke the method (showPicker()). I missed all of this at first because MDN only mentions dialog and popover.
Open UI also alludes to commands for <input type="file">, <input type="number">, <video>, <audio>, and fullscreen-related methods, but I don’t think that anything is certain at this point.
So, what would be the benefits of invoker commands?
Well, a whole lot less JavaScript for one, especially if more invoker commands are implemented over time. Additionally, we can listen for these commands almost as if they were JavaScript events. But if nothing else, invoker commands simply provide more ways to interact with APIs such as the Dialog and Popover APIs. In a nutshell, it seems like a lot of “dotting i’s” and “crossing-t’s” which is never a bad thing.
#2024#API#APIs#approach#Articles#attributes#audio#chrome#command#CSS#custom properties#deal#details#dialog#event#Events#experimental#eye#Features#firefox#how#HTML#it#JavaScript#learning#LESS#listening#logic#loop#mdn
0 notes
Photo
PORTO ROCHA
522 notes
·
View notes
Photo
HRH The Princess of Wales in Southport today, on her first engagement since completing chemotherapy. It’s so good to see her!❤️ --
#catherine elizabeth#princess catherine#princess of wales#princess catherine of wales#catherine the princess of wales#william arthur philip louis#prince william#prince of wales#prince william of wales#william the prince of wales#prince and princess of wales#william and catherine#kensington palace
111 notes
·
View notes
Quote
よく「発明は1人でできる。製品化には10人かかる。量産化には100人かかる」とも言われますが、実際に、私はネオジム磁石を1人で発明しました。製品化、量産化については住友特殊金属の仲間たちと一緒に、短期間のうちに成功させました。82年に発明し、83年から生産が始まったのですから、非常に早いです。そしてネオジム磁石は、ハードディスクのVCM(ボイスコイルモーター)の部品などの電子機器を主な用途として大歓迎を受け、生産量も年々倍増して、2000年には世界で1万トンを超えました。
世界最強「ネオジム磁石はこうして見つけた」(佐川眞人 氏 / インターメタリックス株式会社 代表取締役社長) | Science Portal - 科学技術の最新情報サイト「サイエンスポータル」
79 notes
·
View notes
Photo
AGUST D : DAECHWITA (大吹打) & HAEGEUM (解禁) ⤷ movie posters | ig ; twt (click for hi-res)
#i'm back and ready to create again :'))#bts#bangtan#yoongi#agust d#suga#userbangtan#usersky#bangtanarmynet#hyunglinenetwork#dailybts#*latest#*posters#*gfx#btsgfx#idk if i wanna do an amygdala one#that one seems too personal to edit for me#so these will do for now
698 notes
·
View notes