From 2e2f9bf157b6f1e4342ebb1551f0d2125e767b0b Mon Sep 17 00:00:00 2001 From: Ryan Florence Date: Fri, 18 Aug 2023 21:13:44 -0600 Subject: [PATCH] docs: split styling out into its own section --- docs/guides/styling.md | 813 -------------------------------- docs/styling/bundling.md | 41 ++ docs/styling/css-imports.md | 33 ++ docs/styling/css-in-js.md | 91 ++++ docs/styling/css-modules.md | 37 ++ docs/styling/css.md | 316 +++++++++++++ docs/styling/index.md | 4 + docs/styling/postcss.md | 102 ++++ docs/styling/tailwind.md | 81 ++++ docs/styling/vanilla-extract.md | 48 ++ 10 files changed, 753 insertions(+), 813 deletions(-) delete mode 100644 docs/guides/styling.md create mode 100644 docs/styling/bundling.md create mode 100644 docs/styling/css-imports.md create mode 100644 docs/styling/css-in-js.md create mode 100644 docs/styling/css-modules.md create mode 100644 docs/styling/css.md create mode 100644 docs/styling/index.md create mode 100644 docs/styling/postcss.md create mode 100644 docs/styling/tailwind.md create mode 100644 docs/styling/vanilla-extract.md diff --git a/docs/guides/styling.md b/docs/guides/styling.md deleted file mode 100644 index 57ac311cbb2..00000000000 --- a/docs/guides/styling.md +++ /dev/null @@ -1,813 +0,0 @@ ---- -title: Styling ---- - -# Styling - -The primary way to style in Remix (and the web) is to add a `` to the page. In Remix, you can add these links via the [Route Module `links` export][route-module-links] at route layout boundaries. When the route is active, the stylesheet is added to the page. When the route is no longer active, the stylesheet is removed. - -```tsx -export const links: LinksFunction = () => { - return [ - { - rel: "stylesheet", - href: "https://unpkg.com/modern-css-reset@1.4.0/dist/reset.min.css", - }, - ]; -}; -``` - -Each nested route's `links` are merged (parents first) and rendered as `` tags by the `` you rendered in `app/root.tsx` in the head of the document. - -```tsx filename=app/root.tsx lines=[1,7] -import { Links } from "@remix-run/react"; -// ... -export default function Root() { - return ( - - - - {/* ... */} - - {/* ... */} - - ); -} -``` - -You can also import CSS files directly into your modules and Remix will: - -1. Copy the file to your browser build directory -2. Fingerprint the file for long-term caching -3. Return the public URL to your module to be used while rendering - -```tsx filename=app/root.tsx -// ... -import styles from "~/styles/global.css"; -// styles is now something like /build/global-AE33KB2.css - -export function links() { - return [{ rel: "stylesheet", href: styles }]; -} -``` - -Remix also has built-in support for the following: - -- [Tailwind][tailwind-2] -- [PostCSS][post-css] -- [CSS Modules][css-modules] -- [Vanilla Extract][vanilla-extract-3] -- [CSS side-effect imports][css-side-effect-imports-2] - -## CSS Ecosystem and Performance - -In general, stylesheets added to the page with `` tend to provide the best user experience: - -- The URL is cacheable in browsers and CDNs -- The URL can be shared across pages in the app -- The stylesheet can be loaded in parallel with the JavaScript bundles -- Remix can prefetch CSS assets when the user is about to visit a page with ``. -- Changes to components don't break the cache for the styles -- Changes to the styles don't break the cache for the JavaScript - -Remix also supports "runtime" frameworks like styled components where styles are evaluated at runtime but don't require any kind of bundler integration--though we would prefer your stylesheets had a URL instead of being injected into style tags. - -The two most popular approaches in the Remix community are route-based stylesheets and [Tailwind][tailwind]. Both have exceptional performance characteristics. In this document, we'll show how to use these two approaches as well as a few more. - -## Regular Stylesheets - -Remix makes writing plain CSS a viable option even for apps with a lot of UI. In our experience, writing plain CSS had maintenance issues for a few reasons. It was difficult to know: - -- how and when to load CSS, so it was usually all loaded on every page -- if the class names and selectors you were using were accidentally styling other UI in the app -- if some rules weren't even used anymore as the CSS source code grew over time - -Remix alleviates these issues with route-based stylesheets. Nested routes can each add their own stylesheets to the page and Remix will automatically prefetch, load, and unload them with the route. When the scope of concern is limited to just the active routes, the risks of these problems are reduced significantly. The only chances for conflicts are with the parent routes' styles (and even then, you will likely see the conflict since the parent route is also rendering). - -### Route Styles - -Each route can add style links to the page, for example: - -```tsx filename=app/routes/dashboard.tsx -import styles from "~/styles/dashboard.css"; - -export function links() { - return [{ rel: "stylesheet", href: styles }]; -} -``` - -```tsx filename=app/routes/dashboard.accounts.tsx -import styles from "~/styles/accounts.css"; - -export function links() { - return [{ rel: "stylesheet", href: styles }]; -} -``` - -```tsx filename=app/routes/dashboard.sales.tsx -import styles from "~/styles/sales.css"; - -export function links() { - return [{ rel: "stylesheet", href: styles }]; -} -``` - -Given these routes, this table shows which CSS will apply at specific URLs: - -| URL | Stylesheets | -| ------------------- | --------------- | -| /dashboard | - dashboard.css | -| | | -| /dashboard/accounts | - dashboard.css | -| | - accounts.css | -| | | -| /dashboard/sales | - dashboard.css | -| | - sales.css | - -It's subtle, but this little feature removes a lot of the difficulty when styling your app with plain stylesheets. - -### Shared Component Styles - -Websites large and small usually have a set of shared components used throughout the rest of the app: buttons, form elements, layouts, etc. When using plain style sheets in Remix there are two approaches we recommend. - -#### Shared stylesheet - -The first approach is very simple. Put them all in a `shared.css` file included in `app/root.tsx`. That makes it easy for the components themselves to share CSS code (and your editor to provide intellisense for things like [custom properties][custom-properties]), and each component already needs a unique module name in JavaScript anyway, so you can scope the styles to a unique class name or data attribute: - -```css filename=app/styles/shared.css -/* scope with class names */ -.PrimaryButton { - /* ... */ -} - -.TileGrid { - /* ... */ -} - -/* or scope with data attributes to avoid concatenating - className props, but it's really up to you */ -[data-primary-button] { - /* ... */ -} - -[data-tile-grid] { - /* ... */ -} -``` - -While this file may become large, it'll be at a single URL that will be shared by all routes in the app. - -This also makes it easy for routes to adjust the styles of a component without needing to add an official new variant to the API of that component. You know it won't affect the component anywhere but the `/accounts` routes. - -```css filename=app/styles/accounts.css -.PrimaryButton { - background: blue; -} -``` - -#### Surfacing Styles - -A second approach is to write individual css files per component and then "surface" the styles up to the routes that use them. - -Perhaps you have a `; +} +``` + +Since JavaScript runtimes don't support importing CSS in this way, you'll need to add any relevant packages to the [`serverDependenciesToBundle`][server-dependencies-to-bundle] option in your `remix.config.js` file. This ensures that any CSS imports are compiled out of your code before running it on the server. For example, to use React Spectrum: + +```js filename=remix.config.js +/** @type {import('@remix-run/dev').AppConfig} */ +module.exports = { + serverDependenciesToBundle: [ + /^@adobe\/react-spectrum/, + /^@react-spectrum/, + /^@spectrum-icons/, + ], + // ... +}; +``` + +[server-dependencies-to-bundle]: ../file-conventions/remix-config#serverdependenciestobundle diff --git a/docs/styling/css-in-js.md b/docs/styling/css-in-js.md new file mode 100644 index 00000000000..ce847178f2c --- /dev/null +++ b/docs/styling/css-in-js.md @@ -0,0 +1,91 @@ +--- +title: CSS in JS +--- + +# CSS in JS libraries + +Most CSS-in-JS approaches aren't recommended to be use in Remix because they require your app to render completely before you know what the styles are. This is a performance issue and prevents streaming features like [`defer`](../utils/defer). + +Here's some sample code to show how you might use Styled Components with Remix (you can also [find a runnable example in the Remix examples repository][styled-components-example]): + +1. First you'll need to put a placeholder in your root component to control where the styles are inserted. + + ```tsx filename=app/root.tsx lines=[21-23] + import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, + } from "@remix-run/react"; + + export default function App() { + return ( + + + + + + + {typeof document === "undefined" + ? "__STYLES__" + : null} + + + + + + + + + ); + } + ``` + +2. Your `entry.server.tsx` will look something like this: + + ```tsx filename=entry.server.tsx lines=[7,16,19-24,26-27] + import type { + AppLoadContext, + EntryContext, + } from "@remix-run/node"; // or cloudflare/deno + import { RemixServer } from "@remix-run/react"; + import { renderToString } from "react-dom/server"; + import { ServerStyleSheet } from "styled-components"; + + export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, + loadContext: AppLoadContext + ) { + const sheet = new ServerStyleSheet(); + + let markup = renderToString( + sheet.collectStyles( + + ) + ); + const styles = sheet.getStyleTags(); + markup = markup.replace("__STYLES__", styles); + + responseHeaders.set("Content-Type", "text/html"); + + return new Response("" + markup, { + status: responseStatusCode, + headers: responseHeaders, + }); + } + ``` + +Other CSS-in-JS libraries will have a similar setup. If you've got a CSS framework working well with Remix, please [contribute an example][examples]! + +NOTE: You may run into hydration warnings when using Styled Components. Hopefully [this issue][styled-components-issue] will be fixed soon. diff --git a/docs/styling/css-modules.md b/docs/styling/css-modules.md new file mode 100644 index 00000000000..ae1e36e703f --- /dev/null +++ b/docs/styling/css-modules.md @@ -0,0 +1,37 @@ +--- +title: CSS Modules +--- + +# CSS Modules + +To use the built-in CSS Modules support, first ensure you've set up [CSS bundling][css-bundling] in your application. + +You can then opt into [CSS Modules][css-modules] via the `.module.css` file name convention. For example: + +```css filename=app/components/button/styles.module.css +.root { + border: solid 1px; + background: white; + color: #454545; +} +``` + +```tsx filename=app/components/button/index.js lines=[1,9] +import styles from "./styles.module.css"; + +export const Button = React.forwardRef( + ({ children, ...props }, ref) => { + return ( +