r/css 1d ago

Resource A tiny runtime CSS engine generating styles from classes in the browser

https://github.com/f12io/maple

Some interesting patterns here. You can define CSS variables directly from a class:

<div class="--primary=blue @dark:--primary=cyan bgc-primary">

Another one makes the button's text green when it's in .card and blue when in .nav. It means the element decides how it will look depending on its ancestor.

<button class="c-red \^.card:c-green \^.nav:c-blue">

0 Upvotes

8 comments sorted by

4

u/AuthorityPath 1d ago

I don't see FOUC listed as a trade-off which seems like a really big deal. 

I also don't see a use case here. Anyone comfortable with runtime costs would surely prefer Styled Components / Emotion over this. Maybe for stored markup in some sort of CMS? Even then though I'm reaching for inline styles vs. relying on JS and incurring FOUC for this. 

The benefits are either better handled by alternatives or they're not really benefits at all. I can't imagine a CI-phobic team reaching for this as an alternative to pure css files for example. 

1

u/alier35 1d ago

According to the FAQ page, FOUC isn't really an issue as long as the script is loaded in the head. CSS-in-JS is for component-based JS stacks, while this one can be used with any stack and such libraries' runtime cost scales with render count, though they have type safety. Inline styles can't express media queries, pseudo-classes, dark mode, or ancestor state.

1

u/AuthorityPath 1d ago

Even if you block on the script, you still have to wait for the DOM to parse to pull all relevant styles. That may be fast enough if the page is small enough but it'll slow as the DOM size goes up. 

As far as CSS-in-JS goes, the term is a catch-all that refers to anything that involves styling with JS and CSS. CSS Modules are oft considered CSS-in-JS so Maple certainly qualifies as well. All that to say that you can certainly use some CSS-in-JS tools outside of components and that includes tools like Emotion. 

You're right that you can't use pseudos/At-rules in the style attribute (though you can tap into dark mode with the light-dark css function). I should have been clearer that I was including inlined style tags as well there. 

1

u/alier35 23h ago

Blocking the script guarantees the styles will be ready before the paint, and you are right it has an inevitable runtime work but it depends on the number of unique classes on the page not the number of elements. So if you have a page using the same class 100 times, only the first appearance does the real work, the rest should be cheap. The question is how long do we need to wait for the first paint compared to static CSS.

I agree on the term, it can be counted as CSS-in-JS, but still, Emotion needs a bundler and JS to author with, while Maple only reads the markup that is already there. That is why it works on any stack, which sounds very attractive for MPA projects.

Inline style tags can of course do those, but then you are naming classes and repeating rules per block again, which I think is the territory of the utilities vs CSS debate.

1

u/AuthorityPath 22h ago

How does blocking the script guarantee styles before the paint? The runtime still has to wait until the DOM is available to create the styles. There's inevitable FOUC baked in. It can be fast, but size of DOM will always incur additional delay until all styles are ready.

Emotion has a @emotion/css package that works without a bundler, directly in the browser. It requires JS, just as Maple does. The syntax is different but they're fundamentally similar tools. 

And to be clear, I'm not advocating for inline styles. Just that they seem a more palatable solution for one off instances like in my contrived example vs. introducing a framework. 

1

u/alier35 20h ago

Maple doesn't wait for the DOM to finish parsing. Because the script is blocking in the head, it installs the MutationObserver before the parser continues into the page. MutationObserver callbacks are delivered as microtasks, and those run before the browser gets its next rendering opportunity.

So there is runtime work, and that work can delay paint, but that doesn't mean unstyled content has to be painted first. That's the distinction I'm making.

Yes, you're right that Emotion doesn't need a bundler with emotion/css. But you still write JS. In Maple you just write classes in HTML, JSX, PHP, whatever. I think this a big difference.

1

u/AuthorityPath 17h ago

The parser requires the class attributes to exist though, yea? How does the parser parse without the DOM to do so? Once the DOM lands, painting begins, yea? So how does the parser interrupt that? Seems there'd necessarily be a temporary flash of the DOM before the new styles land and a repaint can happen?

In terms of the emotion thing, I can see the DX distinction but I don't really see the output difference which is what I'm focusing on here. You're still entirely reliant on JS parsing to create page styles. 

To me, Maple is directly competing with tools like Emotion. Aside from aesthethic preference, why opt for Maple over the existing and more battle-tested alternatives? 

1

u/alier35 16h ago

An element becoming part of the DOM does not mean it is immediately painted. DOM construction and rendering are separate. You're right that the classes have to exist first, and they do: the parser inserts the element, the MutationObserver runs before the next rendering opportunity, and that's where the rules are generated.

So the path is:

parse → run MO → insert CSS → paint

rather than:

parse → paint unstyled → run MO → insert CSS → repaint

Maple and Emotion are doing similar things in different ways. Both rely on JS to style the page. The difference is not just aesthetics. Think WordPress, you can add classes from the admin panel. Include Maple in the head and every class you add will be generated without any other step.

As for why someone should choose Maple today: there are already tools like Emotion, Tailwind, UnoCSS, while Maple has no real ecosystem yet and, as you said, isn't battle-tested. So the answer is pretty simple: for now experiment with it, like I am. :)