Closed mhevery closed 1 month ago
Will this be able to support:
Good call @samijaber updated the spec:
const hover = CSS$`color.hover: blue`
const hoverOL = CSS$({
'color.hover': blue
});
const smallScreen$ = MEDIA$`screen and (min-width: 480px)`;
const media = smallScreen$`background-color.hover: lightgreen`;
const mediaOL = smallScreen$({
'backgroundColor.hover': 'lightgreen';
});
MEDIA$
is interesting! I'll say that the <CSS_PROPERTY>.<PSEUDO_SELECTOR>
format is confusing and unfamiliar. You'd:
What about, along the lines of MEDIA$
, you include a PSEUDO$
in the spec?
const hover$ = PSEUDO$`hover`;
const hover = hover$`color: blue`
const hoverOL = hover$({
color: 'blue'
});
const smallScreen$ = MEDIA$`screen and (min-width: 480px)`;
const media = smallScreen$(hover$`background-color: lightgreen`);
// not sure how you would combine 2 tagged templates, that part is a bit confusing to me
const mediaOL = smallScreen$(hover$({
'backgroundColor': 'lightgreen'
}));
What about, along the lines of
MEDIA$
, you include aPSEUDO$
in the spec?
ohh! I like that!
What do you think of this:
const hover = CSS$.hover`color: blue`
const hoverOL = CSS$.hover({
'color': blue
});
const smallScreen$ = MEDIA$`screen and (min-width: 480px)`;
const media = smallScreen$.hover`background-color: lightgreen`;
const mediaOL = smallScreen$.hover({
'backgroundColor': 'lightgreen';
});
We need to account for pseudo-selectors that receive arguments... https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child
My suggestion allows arbitrary strings for pseudo selectors, but yours makes things messier. How would we do nthChild(2n + 3)
? smallScreen$['nthChild(2n+3)']
?
CSS$.hover.nthChild('2n+3')`color: blue`
I personally think adding typings to handle all of these could get...cumbersome to maintain. But up to you!
Also, as I think about this even more...would be cool to support attribute selectors, class selectors, and all sorts of other selectors. It makes me wonder if we could have a SELECTOR$
as a catch-all for users to be able to write whichever arbitrary selector, not just a pseudo one:
/* hover pseudo selector */
const hover$ = SELECTOR$`:hover`;
/* <a> elements with a title attribute */
const href$ = SELECTOR$`a[title]`;
/* All <li> elements with class="spacious" */
const spaciousLi$ = SELECTOR$`li.spacious`
But of course, the benefit of your approach is that it comes with types and autocomplete. Having both would be neat maybe?
This is cool! Something I really like about tailwind, having recently taken it up, is that you don’t use fixed values, you follow a theme and you extend it. It also comes with a great starting point. So it enforces some nice best practises
It feels like this could work with tailwind quite well. I wonder if there’s a way to integrate the 2, allowing us to import compiled CSS$ tailwind objects and use them to compose our styles as well. We can then stick with our UI frameworks and current theme and switching over would be a breeze.
I imagine vite could compile class names such as px-8
into an object in a cache called pX8
. And these could be used like this:
import { px8, textWhite } from ‘virtual’
<div class={[px8, textWhite, etc...]}/>
I would see this as beneficial as the qwik-react integration which would make adoption easier and give new projects a leap forward.
is this something we could consider @mhevery ?
I have 2 main things:
and a small thing is: can we have a concrete example with CSS variables?:)
Will CSS$
be able to dynamically generate stuff at build time like what vanilla-extract does? vanilla-extract requires a dedicated .css.ts
to do this.
In vanilla-extract we can do
// ****.css.ts
function generateStyle(config: LayoutConfig) {
// style object is generated here.
}
export const styleA = generateStyle({...});
export const styleB = generateStyle({...});
// Even array
export const styles = [generateStyle({...}), generateStyle({...})];
[ Copying here from Discord ]
Good write up @mhevery. Thoughts:
The css string must be able to support ALL aspects of the text within a css file in of itself to be fully functional and locally scoped. This includes keyframes and media queries to allow the css to be atomically complete. While I love emotion (endless hours with it and stylis)— This is a major deficiency of emotion that breaks atomically scoped styles by globalizing keyframes. It’s a design deficiency that requires special low-level work arounds via stylis to get true local component scope. We’ve had much dialog around this when @wmertens was writing styled VE.
Pure css syntax is critical for easy porting as well as object format, which you’ve covered.
Below is a LONG thread (of 69 messages) worth scanning through containing conversation on the topic between several community members. There are many others messages out-of-band, repo Issues in VE, Emotion, and Stylis on the topic.
Atomic units of style | CSS as a first-class citizen for CSS-in-JS | Locally-scoped at runtime https://discord.com/channels/842438759945601056/842438761287254019/1044407650345046128
I imagine vite could compile class names such as
px-8
into an object in a cache calledpX8
. And these could be used like this:import { px8, textWhite } from ‘virtual’ <div class={[px8, textWhite, etc...]}/>
I would see this as beneficial as the qwik-react integration which would make adoption easier and give new projects a leap forward.
i belive that could be implemented in the user land so it should just work.
Will
CSS$
be able to dynamically generate stuff at build time like what vanilla-extract does? vanilla-extract requires a dedicated.css.ts
to do this.
yes static code generation is the goal here
- Will this also support CSS preprocessors?
Probably not.
- Is it possible to have a way that developers can freely choose the class names instead of generated ones?
Right now no. What would be the purpose of that?
Also I will add some CSS vars examples
As someone who was an active participant in that discussion that @n8sabes linked, here are some of my thoughts:
My initial impression was that this shouldn't be a priority and if it is, it seems like a lot of effort for not a lot of gain. This impression is a result of numerous Qwik talks demonstrating (and perhaps somewhat implying?) that optimising CSS does not lead to a huge performance improvement.
So I don't know for sure how this would work but if CSS$
streams styles as required, would there be an unintended side-effect of causing extra re-flow and re-paint (potential performance degradation if a lot of layout properties are used)? This could lead to "jumpy" layout shifts similar to how scrollbars can cause layouts to "jump" horizontally whenever they appear. Probably only an issue for those using Qwik primarily in an SPA manner.
I also have some concerns about perceived DX improvements.
As @n8sabes rightfully points out, being able to support all CSS properties via the string literal format is very much an expectation that CSS writers have. I believe this is quite important for adoption from those coming from existing CSS tooling like SASS/SCSS, Emotion, styled-components, CSS modules; and perhaps even Vue/Svelte(Kit) users who get to write co-located scoped vanilla CSS right at the component level.
And speaking of co-located scoped CSS, those two frameworks have them in easily collapsible <style>
tags. On the other hand, CSS$
is either used inline within JSX or has to be expressed as a variable first.
Considering how divided people are on Tailwind's verbosity, I doubt people will be using CSS$
inline when it's guaranteed to be even more verbose than Tailwind given the exact same styling. So if we assume that train of thought, it means all of the variables will be declared at the top of the file or you'll just keep getting "variable is not defined" errors. It won't be a problem for simpler components, but for more complex ones, I don't think it would be appealing for dozens of
const styleName = CSS$`
property: value
`
clogging up vertical real estate in their code editors, and worse, no easy way to collapse all of them together. Ironically, also causing everyone to think of names again.
So when that happens, I'm sure all the variables will end up living in their own separate file to be used as design tokens to be imported and composed. At this point, I'd argue that it ends up being a worse version of vanilla-extract since it will require the user to learn additional syntax for CSS queries while not supporting complex selectors.
The current spec also has maintenance problems. CSS has been on a trend of introducing new features such as :is
, :has
and :where
. You'd have to be constantly aware of such updates and implement them to fit the current spec if such features gain popularity or face churn as people just return to writing more of regular CSS as it grows more powerful. CSS custom properties changed the game so much that even SASS received a dip in popularity, and with CSS getting built-in nesting in the future? I'm not sure if it spells well for a spec that introduces a new syntax.
There's already excellent CSS tooling or frameworks that help tame the CSS beast. Tailwind proudly boasts that most projects end up using less than ~10kb of CSS in their docs and while I do see JS bundle sizes trending upwards, I can't say the same for CSS when looking at Tailwind's growing popularity. Also, to add to the examples already provided, I recently chanced upon another one that could be interesting to watch: https://css.master.co/
I just want to say that I sincerely believe Qwik is pushing the web forward with some truly novel innovation, in fact, pushed it so hard that a once lurking hobbyist like me got so excited about the future of web dev that he's now actively participating in contributing to open source over at Qwik UI. If anything, my opinion is largely observation as I don't have professional experience in the industry but I hope I provided some useful feedback!
Thank you @KenAKAFrosty for providing the ChatGPT analysis. Below are excerpts of the summary for those whom do not wish to read the extensive dialog in just one of the threads on this topic —
@media queries
but needs @keyframes
support.@keyframes
should be supported by VE like @media
is.@keyframes
.The analysis covered many high-level points discussed in the thread, but much is out of band in VE, Emotion, or Stylis Issues. A few points:
See Discord for @KenAKAFrosty's ChatGPT analysis.
Here is an index.tsx
file to play with that demonstrates compiling / tokenizing a whole style and its dependencies as an atomic unit (file / text block). As you can see, it's easy to walk the compiled Abstract Structure and set component-scope as a WHOLE ATOMIC unit.
I was looking back at my old code from 2020 that scopes everything, but it's highly customized for @emotion
+ stylis
. There are some minor gotchas in the animation
shortcut property vs. animation properties broken out into individual attributes, but very easy and doable.
Example 2 in the code below is just an idea for a default style
using floating un-classed style properties in the css-file/text-block. To use this, 1) After scoping, separate the floating properties and send them to the tag, 2) then inject an inline style ahead of the tag for all the rest (named classes, id, keyframes, media queries, etc.). This way children can use the scoped classes by name / id albeit with a little useId()
alchemy helper functions.
NOTE: I believe I used emotion js-object structures and its serializeStyles
helpers for serializing js-objects, but there may be other / better alternatives for js-object structures 🤔. All CSS-in-JS libraries seem to have complicated structures that are not intuitive when translating elaborate (pure) css to js-objects, thus the need to support pure old-school css. I believe you even state this challenge in your original Issue post, suggesting they may not be supported.
import { $, component$, useId } from '@builder.io/qwik';
import type { DocumentHead } from '@builder.io/qwik-city';
import { compile, serialize, stringify } from 'stylis'
export default component$(() => {
const componentId = useId();
const experiment1 = $(() => {
// debugger;
const cssString = `@keyframes fadeIn { 0% {opacity:0;} 100% {opacity:1;} } .oBox{ opacity: 1; box-sizing: border-box; animation: fadeIn 5s;}`;
const compiled = compile(cssString)
console.log(compiled);
const serialized = serialize(compiled, stringify)
console.log(serialized);
});
const experiment2 = $(() => {
// debugger;
// 🤔 Maybe floating styles not contained within a class could be used as a default for style
const cssString = `@keyframes fadeIn { 0% {opacity:0;} 100% {opacity:1;} } .oBox{ color: red;} opacity: 1; box-sizing: border-box; animation: fadeIn 5s;`;
const compiled = compile(cssString)
console.log(compiled);
const serialized = serialize(compiled, stringify)
console.log(serialized);
});
return <>
<div>Stylis Experiments -</div>
<div>
<button onClick$={() => { experiment1() }}>Experiment 1</button>
</div>
<div>
<button onClick$={() => { experiment2() }}>Experiment 2</button>
</div>
</>
});
export const head: DocumentHead = {
title: 'Syle Experiments',
meta: [
{ name: 'description', content: "Syle Experiments" },
],
};
Does anybody know in general how CSS manipulation influences browser resource use?
For example, if you do a class per single CSS attribute+value, you end up with a very long classList on every element. How does the browser cope with this?
Another thing I'm wondering about is, how happy is the browser when you add/remove new styles incrementally. Does every rule add mean work, so it's better to do it in batches? Or does it only matter when the rules are used?
Any articles about this welcome.
BTW, I know that FB uses XStyle and I checked it out in the browser, looks like they have a bug where they produce the same CSS rules in multiple style blocks. Something to watch out for then ;-)
I don't think a solution is too far afield from where we are now with the core. While working with this a couple months ago, I remember the hash-prefix scoping doing a good job on the css file. If not, the stylis
approach shared above works great.
The question is, who has the best css js-object schema used for Css-in-JS to learn from (or adopt). I liked Emotion best, which may have evolved, or there may be better object schemas today I'm unaware of.
BTW -- Here is an example of some complex css that I cannot even think of how to achieve in a JS-Object, thus the need to support pure css syntax:
/* @n8sabes; Inspired by https://codepen.io/tonycorp/pen/JRLaKw */
.float-label {
display: block;
position: relative;
appearance: none;
-webkit-tap-highlight-color: transparent;
padding: 2px;
}
/*
Label when reduced (this is the default that will be modified, when not focused)
*/
.float-label label, .float-label>span {
position: absolute;
top: 5px;
left: 10px;
cursor: text;
font-size: 1em;
opacity: 1;
transition: all 0.2s;
user-select: none;
color: rgba(0, 0, 0, 1);
}
.float-label>span[data-invalid] {
color: red;
}
.float-label input {
font-family: 'Courier New', Courier, monospace;
font-size: 1.75em;
padding: 0.8em 4px 0.5em 10px;
/* Underline Style */
/* border: 0;
border-radius: 0;
border-bottom: 2px solid rgba(0, 0, 0, .1); */
/* Box Style */
box-sizing: border-box;
border-radius: 8px;
border: 2px solid rgba(0, 0, 0, 0.5);
}
.float-label input::placeholder {
opacity: 1;
transition: all 0.2s;
}
.float-label input:placeholder-shown:not(:focus)::placeholder {
opacity: 0;
}
/* Full Size Label, this modifies the small size */
.float-label input:placeholder-shown:not(:focus)+* {
font-family: 'Courier New', Courier, monospace;
font-size: 1.75em;
left: 2px;
top: 2px;
padding: 0.8em 4px 0.5em 10px;
}
.float-label input:focus {
outline: none;
border-color: rgba(0, 0, 0, .5);
}
.float-label input[aria-invalid=true] {
border: 2px solid rgba(255, 0, 0, 1);
}
.float-label input[nospinbuttons]::-webkit-outer-spin-button, .float-label input[nospinbuttons]::-webkit-inner-spin-button {
margin: 0; /* Fix margin even though it's hidden */
-webkit-appearance: none;
-moz-appearance:textfield; /* Firefox */
}
I'd like to respond to a few things in here when I have more time, but I thought it very imperative to stop and point something out.
e.g.,:
Some things may address both simultaneously which is great!
Something to think about is lazy loading might not be that important with atomic styles!
Scalability can be achieved anyway!
My ideal solution looks like:
Pease watch the two videos for the Qwik Style Lab I built for this topic.
Video 1 - Namespace and CSS$ default styles
NOTE: All styles that are the same will have a hash to prevent duplication (atomic / one instance). Variables should be css variables to make the styles dynamic.
Just my opinions fwiw - notes on the RFC after thinking about it for a bit.
TLDR:
Not sure inlining styles is something I would choose but one size doesn't fit all. I do like atomic styled components, feels like a good balance between readable code and minimal cognitive load.
Consider this example:
const CardBackground = styled.div` ...rules... `;
const CardTitle = styled.h1` ...rules... `;
const Card = function(props) {
return (
<CardBackground>
{ title && <CardTitle>{title}</CardTitle> }
</CardBackground>
);
};
Why I prefer atomic components over inline styling or styling functions;
The Card
is just logic. It's easy to read, no styles. The atomic components CardBackground
and CardTitle
are style only and create building blocks. We do this in traditional css/js component development anyways. This skips a bunch of the cognitive load the RFC calls out without sacrificing the readability of the primary stateful component.
When we designed the Fluent UI v9 component library, one big issue we encountered was delivering components which could be easily reskinned in a type-safe manner, without "overriding" the existing styling. Having the styles inline would strongly tie styles to components - even imports effectively hard-code the styling dependency.
We ended up with a model where each component could be imported as an out-of-box component, or you could recompose it by importing the style hook, state hook, and render function separately and using or replacing any of them.
A style hook would look like:
...which looks a lot like some of the examples above.
import { makeStyles } from 'griffel';
export const useButtonClasses = makeStyles({
root: {
':active': { color: 'pink' },
':hover': { color: 'blue' },
// 💡 :link, :focus, etc. are also supported
':nth-child(2n)': { backgroundColor: '#fafafa' },
},
etc.
});
function Button = (props) => {
const classes = useButtonClasses();
const state = useButtonState(props, classes);
return renderButton(state);
}
This lets the devs reuse parts and refactor new component variations and extend or replace the style parts. It certainly has more cognitive load than whipping out some atomic components. This is valuable for shared components that need reskinning, but less so for basic websites that don't need reusability.
This is just something on my mind when I look at Qwik's styling approach, and thinking about how something like this could be achieved. I think I tried this with useScopedStyles$
and ran into issues.
First, I want to call out @layershifter who pointed me to his notes on atomic css used by Griffel and the tradeoffs encountered:
https://griffel.js.org/react/guides/atomic-css
There are some considerations in those notes to call out: in particular, the costs of recalc-ing class names and the overhead they incur is something to pay attention to.
The whole goal of atomic css is to reduce total class definitions. However, this requires that the app is sufficiently large to reach an inflection point. At the start, the atomic approach will result in far more class name definitions and chunkier rendering costs. At an inflection point, you will see less classes overall (assuming the stylesheets are extracted/deduped across the full app graph.)
Compare a stream of html and css with 1 rule that has 3 properties, vs the same with atomic:
.abc123 { background: red; color: black; font-size: 1em }
<html><div class="abc123">Hello</div></html>
Same but with atomic:
.abc123 { background: red; }.abc124 { color: black; }.abc125 { font-size: 1em }
<html><div class="abc123 abc124 abc125">Hello</div></html>
It's pretty clear with this simple example that atomic is going to end up being far more expensive when the stylesheet is trivial, but at some inflection point, if enough components re-use display: block
the theory is that it will result in less insertRule
calls. A logarithmic curve.
When I think about the design goals of Qwik, the premise is to load only the content you need, and ideally render as much on the server as possible and resume only what's needed. That is, it is an anti-goal to ever reach that inflection point where atomic starts seeing gains. The absolutely most efficient experience will be one which loads the least amount of html, css, and js to achieve the desired experience.
CSS$
is dollar signed. Is it a split point? Will that add more network hops?:hover
type-safe. If the css can be linted with css lint, that would probably catch things.a > li > foo
, it's bad to do that. But also global.css is an antipattern. These kinds of things are usually overriding some style, which is a side effect of bad customizability (which is why we opted to publish components that are refactorable.) But also then sometimes you can't refactor the component; it's coming from some 3rd party package. How can we make re-styling safer in these situations without sacrificing type safety and scoping? I don't have a great answer here beyond some standardized DI system for components... which adds lots of complexity.@dzearing, Good points and appreciate the depth of your thought process on this. If I understand you correctly, I 100% agree a styled component is a fundamental feature that needs to be supported. @wmertens wrote a styled
component for vanilla-extract which provides the ability for the CardBackground
& CardTitle
component example.
Expanding upon this, the styled component should accept both standard css and js-in-css object formats, and include all things required to make a whole unit of style (e.g. including keyframes, etc.).
The atomic aspect (using hashes to store exactly one instance of a whole style) is of great value, and the next question is how to also enable a locally-scoped style stack the ability to walk up contexts for css-based template / component libraries.
I likely need to re-read what you wrote another time or two to fully digest what you posted.
@n8sabes Sorry for the long winded post; great to hear that styled
is in the works. It really reduces cognitive load.
The atomic aspect (using hashes to store exactly one instance of a whole style) is of great value, and the next question is how to also enable a locally-scoped style stack the ability to walk up contexts for css-based template / component libraries.
Probably the biggest point from above: regarding using atomic css (1 class = a predictable hashed selector class + 1 name/value pair):
What value are we expecting to get, and will it end up being worth the tradeoffs?
We are rolling out atomic css usage at Microsoft, and we are hitting issues. It is worth exploring if Qwik plans on going down the same path, but I'm not sure if that is the case. There's a lot in the RFC and I might have missed some critical point so if I'm understanding the proposal correctly, here's what I got:
The css is split into small js modules imported by the source. This means that the css rules can be tree-shaken and included with the split points generated by the optimizer. Then later, there's an extraction step which pulls the css out of the .js files and into .css files, so that the rules can be loaded in parallel to scripts.
If that's right, here are my questions:
And, fwiw, my concerns:
I'd like to inject the topic of Semantic CSS patterns that leverage ARIA properties into this conversation. It's really an interesting and powerful means of creating near fully-functional components with no (or very little) javascript. Here is a css rule example:
[role="tab" ][aria-selected-"true"] {
background: var(--clr-neutral-100);
color: var (--clr-neutral-900);
}
Kevin Powell does a great job of explaining it, albeit he's more of a CSS Wizard than a JS coder: How to write Semantic CSS
@dzearing, You've brought up some great points and questions. A conversation would be of value on this topic.
Interesting tidbits around atomic CSS:
CSS$:
$
is required to make it auto-import the CSS?
css
, it would have better editor support.<style/>
tags while streaming SSR?styling libraries:
useStyle$
it to make it lazy.@dzearing I think libraries should accept class names that they apply where needed, that allow "styling" CSS.
@wmertens 100% agree, devs need an escape hatch. You're also right about single character class names causing clashes with multiple independent projects on the page. Gotta use hashing to keep the class representations predictable across independent sources in order to support scenarios akin to module federation or pre-bundled artifacts. Hadn't thought about using unicode in the classname, interesting but sounds painful to debug.
Hi, I'm really sorry for interruption, but my comment won't add nothing new to this discussion. But I really like this idea, which would be potentially the best solution for handling CSS in terms of performance, CWV etc...
My question is: Is there is any ETA when this feature would be done? I mean, are we talking in months, year, 2 years? And possibly what can we do to push this feature forward in roadmap priorities?
Thanks !
Hey @Roman-Simik fair question
Recently we've been looking a lot into Panda CSS, @manucorporat even created a vite plugin to make sure we can integrate with it.
From all of the solutions, it looks like the one who provides the combination of all the benefits we discussed. So it's still in research but you can start playing with that by following this guide: https://qwik.builder.io/docs/integrations/panda-css/#panda-css
I imagine we'll have something more ready in terms of usage and examples in the near future
Hey @Roman-Simik fair question
Recently we've been looking a lot into Panda CSS, @manucorporat even created a vite plugin to make sure we can integrate with it.
From all of the solutions, it looks like the one who provides the combination of all the benefits we discussed. So it's still in research but you can start playing with that by following this guide: https://qwik.builder.io/docs/integrations/panda-css/#panda-css
I imagine we'll have something more ready in terms of usage and examples in the near future
Hi, thanks for the fast response and suggestion.
@Roman-Simik the CSS landscape for Qwik is still settling down.
Overview:
So personally, I'd use Panda, unless I would be on a tight deadline with no time to experiment. In that case I'd pick Tailwind.
I'm now more inclined towards UnoCSS for its speed and simplicity, although https://github.com/atlassian-labs/compiled looks extremely interesting too.
At a higher level, there's no full component libraries for Qwik yet (that I know of) so you would either make your own or use a JS-less UI like daisyui. https://qwikui.com/ is still under heavy development.
Hi, thanks a lot for the fast responses, really helpfull info !
@mhevery The link to StyleX in your original post should probably go to the official Facebook/Meta StyleX library (yet unreleased, but they want feedback on their docs site), since I presume you meant Facebook's Atomic CSS called StyleX, which you also linked to.
If you're using Vanilla Extract, I ported some helpers I used with React.
const redBorderFromString = CSS$` border: 1px solid red; border-radius: 50% `; const redBorderFromObjLiteral = CSS$({ border: '1px solid red', border-radiues: '50%', });
Both of the above examples are equally supported and are identical.
There is a small use case that is only supported by redBorderFromString
: fallback values for the same property:
height: calc(100vh - 100px);
height: calc(100dvh - 100px);
Browsers that do not understand the value (the dwh
unit in this example) will pick the last declaration they understand.
@fabb I believe most css convertors support passing an array for multiple values of the same prop
@fabb I believe most css convertors support passing an array for multiple values of the same prop
In panda css not, because there array syntax is already used for responsive values: https://github.com/chakra-ui/panda/discussions/1109
Constraints
The
CSS$
will be able to refer to static content only. So things like this will not be supported and will be a compiled error.:const redBorder = CSS$`border: ${Math.rand()}px solid red`;
If the CSS needs to have variable, than CSS variables should be used.
Does this mean css cannot be dependent on props? Or do we just need to use conditionals to pick between different css depending on a prop?
e.g.
const redBorder = CSS$`border: 1px solid red`;
const greenBorder = CSS$`border: 1px solid green`;
export const Greeter = component$<{ error: boolean }>((props) => {
return <span class={props.error ? redBorder : greenBorder}>Hello</span>;
});
We moved this issue to qwik-evolution
repo to create a RFC discussion for this.
Here is our Qwik RFC process thanks.
RFC: Lazy-loadable, type-safe, scalable style support
[ HackMD | #2767 ]
Goal
Have an ergonomic way of styling components where the styles are:
Prior Art
A quick overview of existing styling solutions and their pros-cons.
Legend:
Influence
There is an interesting article on Atomic CSS called StyleX This points out that as applications get very large the need for additional CSS reaches zero growth. This is because the styling is broken up into primitives which are then reused. We think this is a good approach for Qwik as well.
Proposal
The basic idea is to create a
CSS$
tagged string literal which can be used as:Both of the above examples are equally supported and are identical. Advantages between the two approaches are:
redBorderFromString
: devs can cut&paste from the dev-tools. Downside is that to get code completion they have to install an editor plugin.redBorderFromObjLiteral
: TypeScript can verify types, but cut&paste would not work from dev-tools/existing CSS.Pseudo Selectors and Media Queries
Return value
The return value of
CSS$
is an opaque object which can contain 1 or more classes along with the associated QRLs. The returned values can be composed together in markup.Transformation
As you can see the
CSS$
ends with$
which means it is subject to optimizer and lazy-loading.Will be transformed to:
file:
HASH_OF_JS
NOTE: exact implementation to be determined and may be different.
Runtime & SSR
The Qwik runtime will be able to recognize the strings which are QRLs and will know to load a specific JS files and create
<style>
tags from those JS files.The SSR will insert the
<style>
tags into the corresponding SSR output.Because the styles are just JS loaded through QRLs, existing prefetching and bundling system will be able to optimize the loading of the styles.
The runtime can easily see which styles have already been loaded and which still need to be loaded.
No need for
useStyle$()
With the
CSS$
approach there is no need foruseStyle$()
to load the styles. The renderer is now intelligent enough to recognize when a QRL is being passed into theclass
and if it needs to be loaded. Because the rendering can delay flushing of the UI to DOM, the renderer can load the CSS without causing a flash of unstyled content.Constraints
The
CSS$
will be able to refer to static content only. So things like this will not be supported and will be a compiled error.:If the CSS needs to have variable, than CSS variables should be used.
Thoughts on CSS
CSS selectors can have complex rules such as
body>ul>li
. We think such rules are very hard to reason about and make the CSS append only as devs are worried that changing them will break something. Such rules are also hard to tree-shake for.We think for styling components such rules are an anti-pattern and will not be supported by the
CSS$
which has one-to-one connection.Instead if you want to use such complex rules,
global.css
is a good place to put them, but you lose the ability to lazy load such rules.Advantages
CSS$
can be composed together or grouped into arrays and referred to by other JSX.