@o/pipeline
Operate’s design system
Installation
Note
@o/pipelineis currently only available for use within theoperaterepo.
-
Add the package to your app or package in your
package.jsonfile, installing any necessarypeerDependencies:"dependencies": { "@o/pipeline": "workspace:*", } -
Add the following to your project’s
.gitignore:Note
This should be taken care of already in the monorepo’s
.gitignore./public/.pipeline -
Add a
preparescript in your project’spackage.json:{ "scripts": { "prepare": "pipeline prepare" } }This script will automatically copy
@o/pipeline’s static assets to your project under/public/.pipeline. -
Ensure
/public/.pipelineare aggressively cached{ "headers": [ { "source": "/.pipeline/(.*)", "headers": [ { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" } ] } ] } -
Inside your main
.cssentry point (i.e. where you’re usingtailwindcss) import the Pipeline theme and declare as an external source:@import "tailwindcss"; ++ @import "@o/pipeline/theme.css"; ++ @source "../node_modules/@o/pipeline"; -
In your root layout, import your main
.cssentry point (in this example,globals.css) file, and wrap your app with thePipelineProviderFor example, in a Next.js app:
import { PipelineProvider } from "@o/pipeline/provider"; import "../styles/globals.css"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <PipelineProvider> <html lang="en"> <body>{children}</body> </html> </PipelineProvider> ); } -
Unless overriding
fontFamily.sans, initialize the fonts.Note
Font assets are available under
src/assets/fontsFor example, in your Next.js root
layout.tsx:// layout.tsx import { PipelineProvider } from "@o/pipeline/provider"; ++ import localFont from "next/font/local"; ++ const muoto = localFont({ ++ src: "../node_modules/@o/pipeline/src/fonts/muoto-regular.woff2", ++ variable: "--font-muoto", ++ }); export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <PipelineProvider> -- <html lang="en"> ++ <html lang="en" className={muoto.variable}> <body>{children}</body> </html> </PipelineProvider> ); } -
Set up a framework-appropriate theme provider to handle switching between
.lightand.darkclasses, as well as ensuringtheme-colormatches the computed style ofproperties.canvas(from@o/pipeline/styles/colors) on change.Ideally, this should respect the user’s system preferences by default, and allow for manual overrides.
-
Add the
Toastercomponent to your app, ensuring thethemeis specified based on your theme providere.g. For Next.js, you may need to create a separate component that references the
useThemehook from your theme provider// components/ThemedToaster.tsx "use client"; import { Toaster } from "@o/pipeline/components/toast"; import { useTheme } from "next-themes"; import React from "react"; export function ThemedToaster() { const { resolvedTheme } = useTheme(); return ( <Toaster theme={ resolvedTheme as unknown as React.ComponentProps< typeof Toaster >["theme"] } /> ); }// layout.tsx import { PipelineProvider } from "@o/pipeline/provider"; import localFont from "next/font/local"; ++ import { ThemedToaster } from "./components/ThemedToaster"; const muoto = localFont({ src: "../node_modules/@o/pipeline/src/fonts/muoto-regular.woff2", variable: "--font-muoto", }); export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <PipelineProvider> <html lang="en" className={muoto.variable}> <body>{children}</body> </html> ++ <ThemedToaster /> </PipelineProvider> ); }
Architecture
Colors
Tailwind v4 introduced a new CSS-driven approach to theming, marking their previous JS-based configuration as a “legacy” system. Additionally, Tailwind’s JS-based plugin system doesn’t (yet) provide any way to inject styles into the @theme layer.
While Tailwind do provide documentation on how to resolve theme values, it’s approach comes with the following caveats:
- It’s browser-only
- No type safety
- No way to map over all color values (e.g. for something like a “color picker”).
To mitigate this, Pipeline stores its color palette in src/styles/colors.ts
Upon pnpm i, this gets compiled into dist/styles/colors.css, allowing for full compatibility with Tailwind (including IntelliSense)
Additionally, Pipeline surfaces these colors at @o/pipeline/styles/colors, allowing them to be consumed as:
-
Raw
root(light) anddarkcolor valuesimport { root, dark } from "@o/pipeline/styles/colors"; const color = root.gray[500]; // "oklch(…)" const darkColor = dark.gray[500]; // "oklch(…)" -
CSS variables
import { vars } from "@o/pipeline/styles/colors"; const color = vars.gray[500]; // "var(--color-gray-500)" -
CSS properties
import { properties } from "@o/pipeline/styles/colors"; const color = properties.gray[500]; // "--color-gray-500"
Editing Colors
To rebuild the colors.css file after making changes, run pnpm build (or pnpm i)
Assets
All assets should be suffixed with a version number to avoid caching issues.
Documentation
@o/pipeline’s documentation is surfaced at design.operate.so
We use a bespoke Astro-based site to surface each component’s README.mdx file
as its own page
Each page should aim to provide a minimum of:
- Installation instructions
- Use examples (or “stories”)
- Design or implementation notes
Writing Stories
All stories should be written in the relevant src/components/component/_stories.tsx file
Each story should be wrapped in the Story component to ensure style and behavior isolation
import { Story } from "@o/pipeline/docs/story";
import { Component } from "@o/pipeline/components/component";
export function Base() {
return (
<Story>
<Component />
</Story>
);
}
Stories can be imported and rendered in the component’s README.mdx file as follows…
---
title: Component
slug: components/component
status: undocumented
---
import * as Stories from "./_stories";
## Usage
<Stories.Base client:load />