The CSS problem

Just to be extra clear and avoid confusion: shadow-css does not use tailwind, it only borrows the naming scheme of the aliases. And again they are optional.

I think this is nonsense. You are welcome to try, but you are not going to build a more efficient :hover implementation in code. Not line-of-code wise, not performance-wise, not in any other meaningful way. These are so extremely common, that you’d end up building something similar just with more code and slower.

Just as a thought experiment express (css :px-4 [:lg :px-8]) in “better”. This sets padding-left and padding-right to 1rem, until the viewport reaches 1024px, then it is 2rem. The plain "px-4 lg:px-8" tailwind classes, which is even shorter but meh strings.

Of course as with everything you can go overboard with these, at which point code may actually become better. For the most common cases this just isn’t the case though.

What? I don’t even? Every single tailwind page I have ever created, or looked at from others, uses flexbox extensively. Show me a single tutorial/guide/example with any kind of layout that doesn’t use flexbox.


well… you did build shadow-css so I assumed you knew flexbox… unless you’re using tables… which would be even more amazing. Your audience are the tailwind people but I’ll just quote biggie smalls: “Never get high on your own supply”

Sometimes you don’t need a more ‘efficient’ hover but a more controlled’ hover, or a more controlled :focus. check out all of the 06-ui-* links on Native Components, like 06-ui-button or 06-ui-input for examples of controlled animation. 06-ui-toggle is the best example of some random indicator that you may need to implement. or maybe a rolling animation like 03a-model-roller Sometimes you need staggered animation, sometimes you need mixed effects. Good luck doing that in css.

My audience is Clojure people. I have no interest in converting even a single current Tailwind user. Tailwind is just a convenient aliasing mechanism that I “borrowed”, that again if this isn’t entirely clear by now is optional.

Audience is also pushing it. I built this for myself, if no one else uses it that is fine by me.

I’m waiting.

Note that one doesn’t make the other true. There are uses for both techniques. As you say “Sometimes …”. Pick the best tool for the job, don’t throw away options.

@zcaudate: You’re talking about pseudo-selectors and control, but you’re referring to react native. The web and native mobile are two completely different things. One (mobile) gives you full programmatic control over the ui, while the other doesn’t. I think it’s important to acknowledge that. Trying to do fully scripted control in the web browser yields terrible performance, and you’ll have to “invent” everything since the platform doesn’t help you very much (at least not yet).

About Tailwind. Tailwind isn’t a framework. It’s a library, so you can use it at your leisure. It’s not even tied to any view library/framework (react/vue/whatever), which is a plus. (contrast that with Material UI which is deeply tied to React, which can lead to real headaches when you want to upgrade React). If you’re afraid of the impact of migrating to some different library, well then you should make component abstractions (i.e. components for all the building blocks of your ui), that’ll enable you to swap out the implementation of a component with whatever you’d like without impacting the rest of the app.

Anyways, I don’t understand all the hate towards the different ways of doing styling/layout. I think all of them have merit. There’s no one correct way of doing things.


and since we’re quoting ourselves now, here is one that I’m waiting for:

Absolutely! I’d never heard of shadow-css until reading this thread. Very exciting

It’s not hate. I’ve used it, I know it’s limitations and by far what stood out to me what how slow it felt to me. Coming into ui, I had always looked for grid systems before someone else had told me: just learn flexbox. I don’t think there’s anything shameful about not knowing. We get bombarded with people telling us ‘This is the best’ all the time and we sometimes do miss out on the easier methods.

I haven’t used shadow-css and would assume it’s pretty fast. Having said that, the good thing about react native is that it provides a compatibility layer for both web, mobile and desktop. That combination is pretty hard to beat. Plus, there’s really good support for animations, which is pretty hard to beat.

I don’t know why you are linking the reference documentation for Tailwind “grid”. Does that somehow mean “flex” doesn’t exist anymore? You are still encouraged to use both, where they make sense. I’m gonna end this line of discussion here from my end. You made a statement (ie. “don’t know flexbox”) I find categorically wrong, but everyone can make their own judgement.

The shadow-css rationale sort of hints at that.

My problems with regular CSS (or rather SCSS) was the naming problem. Selectors sort of grow over time and it becomes really hard to keep track of what is still used and where/how. Leads to a lot of breakage over time if things are used in multiple places and making a change has unintended consequences elsewhere. Having maintained multiple several year timeframe projects this was the worst. Instead of changing anything you just create a new class, which also never gets deleted. This could be solved by better tooling, but I didn’t have it and didn’t want to write it.

My problems with tailwind mostly stem from everything needing to be part of the <div class="..."> attribute. There is also lot of repetition and humongous long class strings. They are also going way beyond what is reasonable these days, which is mostly due to the system requirement of “this must be a valid CSS selector class”. Mixing this with regular CSS is fine, but has the naming problem again.

I also used inline styles for a while. Works, until you want to target things in a more declarative way, or not reachable via regular properties (eg. pseudo, media). At which point I was mixing regular CSS in again, at which point I had the naming problem again.

Ask me again in 5 years whether I think shadow-css was good or bad. It is just the next attempt in the long line of failures/disappointments. I don’t even encourage anyone to use it yet. Some people are curious though and I’m open to the discussion.

It’s definitely hard to push a new standard. I personally have not bet on cljs so my view on things are probably a little bit pessimistic. However, I do appreciate the shadow ecosystem and the enormous work that goes into it.

The user experience of shadow-cljs for me has absolutely been fantastic and I’d assumed that it would be the same for shadow-css integration. I’ve always made libraries for myself and never really cared about user-adoption - it was probably a bad idea in hindsight. I’m all for doing thing for the sake of doing things - especially with tooling because you just never know when it might just come in handy.

naming is a huge problem and one of the reasons why I started inlining. It’s hard to get it right for layouts → is it side-menu or right-menu? What happens when the side-menu needs to go on the bottom of the screen for mobile? It’s a mess.

Another option is using css-in-js libs. I like using emotion and have a cljs wrapper lib for it

I specifically am using it combined with helix which lets you use the :css property directly as in js emotion - this solves the naming problem and the specificity problem as well, as documented here:

1 Like

Yes, thanks for saying this: in fact the naming scheme is in my head what I most appreciate about Tailwind, and I haven’t found a need for the rest of it.

I don’t think that is necessarily the case. I personally enjoy the feeling of cheap thrills. So coming up with a roller model was actually quite satisfying. It can be used for alot of things involving a rolling motion - tickers, scrollers, carousels. Even in 3d environments. And it’s always good to understand how something works. I don’t think there is such thing as wasted effort.

Performance is not something that is reached on day one. It takes a lot of time and understanding of the general system to be able to affect change. One must then test that understanding because most times things go to shit. You can be lazy I guess and just trust open source libraries - but a dependency is then created and it will only take you so far before you start digging at it for change.

And it’s always good to understand how something works. I don’t think there is such thing as wasted effort.

Totally agree with that.

You can be lazy I guess and just trust open source libraries - but a dependency is then created and it will only take you so far before you start digging at it for change.

I see this a lot. And in js/react-land it tends to turn a project into a quagmire because of all the inter-dependencies.

(Edit: my current project is in this kind of state, and I’ve been working way too long to get us out the sinkhole. But I guess the main issue isn’t the dependencies themselves, but rather the team culture of immediately reaching for a library from the shelf when some new functionality is needed)

1 Like

Don’t know about “brilliant”, and not a standalone CSS solution, but my Web/MX hack (ne mxWeb) offers a radically different solution. Key qualities:

  • it still works with stylesheets via the class attribute;
  • the in-line style of a widget can be defined reactively, via a CLJS formula that keeps up with other app state. Or at least examines other widget state to decide its CSS;
  • so, yeah, we have dynamic CSS without juggling classes; and
  • the style attribute can be expressed as an object with individual reactive properties per style attribute. Prolly not huge performance win, but certainly easier to fine-tune or just understand a widget’s styling.

These examples are from the huge-ish Web/MX demo app “AskHN Who’s Hiring?”. (Live version built with the JS version of Web/MX is here.). Now the examples.

A primitive rule building a style string reactively, to show/hide a star in the header of a job which the user has scored:

(span {:style (cF (str "color:black;max-height:16px;margin-right:9px; display:"
                        (if (or (deets? me)
                              (zero? (memo/job-memo job :stars)))
                          "none" "block")))}
      (utl/unesc "&#x2b51"))

A case that shows dynamic CSS classes, in support of animation, as well as more structured specification of :style:

(div {:class (cF (if (deets? me) "slideIn" "slideOut"))
        :style (cF {:margin     "6px"
                    :background "#fff"
                    :display    (if (deets? me) "block" "none")})}

And a full-blown css-inline model:

(make-css-inline me
    :border "none"
    :font-size "14px"
    :display (cF (if (and (rx-completed rx)
                       (not (rx-due-by rx)))
                   "none" "block"))
    :background-color (cF (when-let [clock (mxu-find-class (:tag @me) "std-clock")]
                            (if-let [due (rx-due-by rx)]
                              (if (rx-completed rx)
                                _cache                      ;; cF expansion has _cache (prior value) in lexical scope
                                (let [time-left (- due (mget clock :clock))]
                                    (neg? time-left) "red"
                                    (< time-left (* 24 3600 1000)) "coral"
                                    (< time-left (* 2 24 3600 1000)) "yellow"
                                    :default "green")))

One thing not shown is that, since Web/MX accepts a map of CSS attributes, it would not be hard to fake in-line CSS composition with a clever set of routines, perhaps by rolling our own fake CSS class tags and driving the final CSS map off that. But that sounds like we would descend into CSS combination hell pretty quickly. There if we want it, tho.

Finally, again, this is not a standalone solution. I offer this more as an example of how much goodness falls out of making widget properties individually reactive. I will yap more about this in the formal announcement of Web/MX. RSN.

Can you explain this? Are you keeping a graph of the widgets? what does the cF macro do? If you are using react, doesn’t it do that already?

Used a few over the years

EDIT: Have not seen shadow-css until now but excited to give it a shot!

However, my choice now is honestly just tailwind. It feels kind of Clojury to me where it has a well-thought out core, that you can make new things by just composing utils from it. Its tooling really does help too given a common problem is “How do you remove unused CSS?” it only emits what’s referenced in your source files, and it only searches for its keywords so it works out of the box with cljs files (or any text lang) provided you specify the extension in the config.

Please feel free to ask any questions about using those.

"Are you keeping a graph of the widgets? " Yes. My Web/MX library, like all Matrix (my state manager) libraries keeps a tree of objects, each a proxy for, in this case, a DOM node.

DOM events act on the proxy tree, which then writes back to the DOM.

" what does the cF macro do?" It defines a “formulaic Cell” that manages whatever property it is associated with. Where the formula (standard CLJS code) reads another Cell, a dependency is established so changes to one Cell know what other Cells to notify.

“If you are using react, doesn’t it do that already?” Web/MX wraps HTML/CSS directly, so it knows in this case it just needs to update the class and style attributes. React regenerates and diffs VDOM, sees what turns out differently, then makes those updates. Less efficient, but ironically the React team now explicitly rejects the reactive paradigm, because they see advantages to controlling updates themselves, and reactive libs update eagerly.

1 Like

Nice! thanks for the writeup.

What does the cF primitive look like and how does it compare to solidjs?

cF simply makes an object (CLJ map) that the rest of the Matrix engine knows how to use to support slot-specific reactive behavior:

(defmacro cF [& body]
     :code '~body
     :value unbound
     :rule (c-fn ~@body)))

The :rule is for the “formula” – really just CLJ code. The Matrix make function, by the by, never lets the Cell hit the slot. It stashes the cell in meta data of the ref that holds the model (another map) of which the cell will be a property.

As for SolidJS, I do know that it, too, brags (deservedly!) on the granularity thing. Do not quote me on this, but I think it achieves this via a compilation that spots dependencies and uses them to generate reactivity.

I have a JS version, of Web/MX, but that lives off fat arrow functions and define_property to achieve transparency.

1 Like