Up to this point, Remix has taken a hands-off approach to styling with anything other than plain CSS. This made sense in the beginning because we took a very different approach to CSS than most other component-driven frameworks. We want users to actually understand how CSS actually makes it to the page, and following the path of other frameworks means introducing some magic that obscures that.
That being said, it's not a viable strategy long-term to stay agnostic about styles. Remix is a full-stack framework, and styling is a key part of the frontend. We need to support tooling to improve and simplify the user experience of authoring styles, and we should do it in a way that does not diverge from our core philosophy.
The main goal is eventual feature parity with other mainstream tools in the JS ecosystem (Create React App, Vite, Next) and full-stack frameworks (Laravel, RoR). This means support (or clean migration paths) for:
- CSS Modules
- PostCSS
- Relative URL transformations
- Sourcemaps
- SCSS
- Bare CSS imports
These are ordered by priority based on cascading difficulty.
Currently in progress. The big remaining hurdles IMO are:
- Resolving relative imports via
@import
orcomposes
- This requires that we process all CSS modules syncronousely before the browser/server builds can continue, as all hashed selectors will need to be known and injected into our JS modules
- Source maps
- This is a bit trickier that it seems on the surface because our
implementation writes the output to a single file vs. separate files or even
separate inline
<style>
tags, which means line-numbers will be off. There are existing solutions in other bundlers we can look to that will hopefully make this pretty straight-forward.
- This is a bit trickier that it seems on the surface because our
implementation writes the output to a single file vs. separate files or even
separate inline
We can release initial experimental support without resolving these for now. This will allow us to get feedback on our API and identify any pain points with migration while we work on this sub-feature.
/* ui/button.module.scss */
.button {
composes: box from "./box.module.css";
appearance: none;
background-color: blue;
}
/* ui/box.module.scss */
.box {
display: block;
}
/* result */
.button__x2jsj {
-webkit-appearance: none;
appearance: none;
background-color: blue;
}
.box__s76k9 {
display: block;
}
// ui/button.jsx
import styles from "~/ui/button.css";
let Button = () => <button className={styles.button} />;
// result
let Button = () => <button className="button__x2jsj box__s76k9" />;
Once CSS Modules are released, we'll already have a pre-processor in place to compile plain CSS as well. Should be relatively simple to introduce.
Under the hood we may use ParcelCSS instead. Devon has considered support for
postcss.config.js
where all PostCSS plugins that enable spec-compliant
behavior would be ported to their compiler. This would trade 100% feature parity
for a significantly faster build, and it would encourage folks to write more
future-proof CSS where eventually the processing may no longer be necessary. We
could write code-mods for commonly used non-spec PostCSS (such as SCSS-style
nesting selectors).
At the moment, url()
functions with relative paths won't work as expected
since they will resolve relative to the assets directory. We should transform
these into absolute paths. We should be able to do this rather simply with our
pre-processors.
/* ui/poop.jpg */
💩
/* ui/poop.css */
.poop {
background-image: url("./poop.jpg");
}
/* result */
.poop {
background-image: url("/_assets/poop__maybe-optimized-hash.jpg");
}
Already mentioned for CSS Modules, we will need this for all pre-processed CSS.
SCSS is still a widely used pre-processor and is supported by basically every other framework we compete with. As we are already opening the box for processing styles, this should relatively simple to support. Essentially we just need to resolve the imports before processing the source and adding the output to our asset manifest.
At the component level, these imports make little sense. Conflicts are likely and cascade ordering is a major footgun. If we ultimately decide to support this for compatibility + ease of migration, we should discourage its usage in our docs and explain the benefits of avoiding it. We very likely will not support bare imports, but rather provide a migration script that does one of the following:
- Convert
.css
to.module.css
(same with.scss
) and rewrite classname references- This could be tricky to implement in a bullet-proof way, but it could get them 80% of the way there
- This is likely undesirable for some, as previously global selectors are now scoped; outside references to those selectors no longer work
- Parse all imported modules to map out and remove existing stylesheet imports,
and generate
remix-global.css
with CSS@imports
that can be imported in the root- This shouldn't change any behavior unless there are cascade issues, but now users have explicit control over the cascade and can quickly make necessary changes