Styling
Get started with styling your extension.
To build the extension interface TurboStarter comes with Tailwind CSS and Radix UI pre-configured.
Why Tailwind CSS and Radix UI?
The combination of Tailwind CSS and Radix UI gives ready-to-use, accessible UI components that can be fully customized to match your brands design.
Tailwind configuration
In the packages/ui/shared/src/styles directory, you will find shared CSS files with Tailwind CSS configuration. To change global styles, you can edit the files in this folder.
Here is an example of a shared CSS file that includes the Tailwind CSS configuration:
@import "tailwindcss";
@import "./themes.css";
@custom-variant dark (&:is(.dark *));
:root {
--radius: 0.65rem;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
...
}For colors, we rely strictly on CSS Variables in OKLCH format to allow for easy theme management without the need for any JavaScript.
Also, each app has its own globals.css file, which extends the shared config and allows you to override the global styles.
Here is an example of an extension's globals.css file:
@import url("https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&display=swap");
@import "@turbostarter/ui/globals.css";
@import "@turbostarter/ui-web/globals.css";
@theme {
--font-sans: "Geist", sans-serif;
--font-mono: "Geist Mono", monospace;
}This way, we maintain a separation of concerns and a clear structure for the Tailwind CSS configuration.
Themes
TurboStarter comes with 9+ predefined themes, which you can use to quickly change the look and feel of your app.
They're defined in the packages/ui/shared/src/styles/themes directory. Each theme is a set of variables that can be overridden:
export const orange = {
light: {
background: [1, 0, 0],
foreground: [0.141, 0.005, 285.823],
card: [1, 0, 0],
"card-foreground": [0.141, 0.005, 285.823],
...
}
} satisfies ThemeColors;Each variable is stored as a OKLCH array, which is then converted to a CSS variable at build time (by our custom build script). That way we can ensure full type-safety and reuse themes across different parts of our apps (e.g. use the same theme in emails).
Feel free to add your own themes or override the existing ones to match your brand's identity.
To apply a theme to your app, you can use the data-theme attribute on your layout wrapper for each part of the extension:
import { StorageKey, useStorage } from "~/lib/storage";
export const Layout = ({ children }: { children: React.ReactNode }) => {
const { data } = useStorage(StorageKey.THEME);
return (
<div id="main" data-theme={data.color}>
{children}
</div>
);
};In TurboStarter, we're using Storage API to persist the user's theme selection and then apply it to the div#main element.
Dark mode
The starter kit comes with a built-in dark mode support.
Each theme has a corresponding dark mode variables which are used to change the theme to its dark mode counterpart.
export const orange = {
light: {},
dark: {
background: [0.141, 0.005, 285.823],
foreground: [0.985, 0, 0],
card: [0.21, 0.006, 285.885],
"card-foreground": [0.985, 0, 0],
...
}
} satisfies ThemeColors;Because the dark variant is defined to use a class (@custom-variant dark (&:is(.dark *))) in the shared Tailwind configuration, we need to add the dark class to the root element to apply dark mode styles.
The same as for the theme color, we're using here the Storage API to persist the user's dark mode selection and then apply correct class name to the root div element:
import { StorageKey, useStorage } from "~/lib/storage";
export const Layout = ({ children }: { children: React.ReactNode }) => {
const { data } = useStorage(StorageKey.THEME);
return (
<div
id="root"
className={cn({
dark:
data.mode === THEME_MODE.DARK ||
(data.mode === THEME_MODE.SYSTEM &&
window.matchMedia("(prefers-color-scheme: dark)").matches),
})}
>
{children}
</div>
);
};You can also define the default theme mode and color in the app configuration.
How is this guide?
Last updated on