Scoped CSS in Remix.run apps
Scenario
While rewriting this website with remix.run and coming from the Angular universe with some React experience, one of the major things I was missing were scoped component styles. The /styles folder got cluttered quickly as I added more and more global styles for the component with class prefixes and so did the root.tsx file. So I decided that it's necessary to do some more research and refactor what I have got so far.
And the solution is actually a lot easier than I expected. Imagine you have got the default folder structure for remix projects in your workspace:
/app
/components
/routes
/styles
/utils
entry.client.tsx
entry.server.tsx
root.tsx
The goal is to be able to create reusable components in the /components folder with their styles right next to them.
How remix styling works
The goal of styling in remix is, to have only the styles defined that are necessary for the page. This is a optimisation for server-side rendering to deliver as few kB as possible. In remix you define a mix of custom components that live inside the /components folder and routing components that live inside the /routes folder. The routing components automatically set up the router tree for the whole application based on the file system structure and the naming of the files.
Based on the remix docs it is recommended to place the stylesheets for these routing components in the /styles folder.
So, how do the styles get recognized by remix? There is a special component called Links
. It's imported in the
root.tsx file by default
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta />
<Links />
<!-- This is the one! -->
</head>
<body>
<!-- ... -->
</body>
</html>
This Links
component get's replaced by all links
functions used in routing components. Let's have a look at these functions.
Right before the component definition you can create a new function called links
. This function returns an array of objects.
Each of these objects must contain the properties { rel: 'stylesheet', href: CSS_IMPORT }
, where CSS_IMPORT
must be replaced
with some css import, like in the following example.
import { LinksFunction } from 'remix';
import globalStyles from '~/styles/global.css';
export const links: LinksFunction = () => [
{ rel: 'stylesheet', href: globalStyles },
];
Example: An alert component
Creating the component
Imagine we write a custom alert component. Then place two files in the /components folder:
/app
/components
/alert
alert.tsx
alert.css
Let's jump in to the tsx file and create the most simple component. It has one property
called message
where we can tell the component which text to display.
interface AlertProps {
message: string;
}
export function Alert(props: AlertProps) {
return <div class="alert">{props.message}</div>;
}
In the next step we want to create really nice looking styles for the alert
.alert {
background-color: red;
border-radius: 8px;
padding: 30px;
color: white;
}
It's time to wire it up. In the first step we create a links
function in the component
directly before the alert interface. JavaScript users can simply drop the type.
import { LinksFunction } from 'remix';
import styles from './alert.css';
export const links: LinksFunction = () => [{ rel: 'stylesheet', href: styles }];
Integrate the component in the parent
Let's say we want to use the component in our root.tsx file for some reason. First step is to import the component and the links function.
import { Alert, links as alertLinks } from '~/components/alert/alert';
and add the alertLinks
to the parent components links
function as follows
export const links: LinksFunction = () => [
...alertLinks(),
{ rel: 'stylesheet', href: globalStyles },
];
There can be other style imports as usual. This works for root.tsx, for other parent custom components that live inside the /components folder, or for routing components.
Conclusion
You can write your scoped styles and place it right next to your custom components. You need
to import them and export the links
function. When you import your custom component in the
parent component (not necessarily a routing component), you import the links
, too. The return
value of the links function (the array of stylesheet objects), will be spreaded into links
object
of the parent component. In the end each route knows exactly which styles are used on the page
and you have fine-grained control about when to use which styles. Since remix.run's focus is
on server-side rendering this makes sense, because we only want to ship to the user what's needed.
No page won't ship with styles that aren't used on it. The way of using CSS imports is also the recommended way in the official docs.