[v4] Split theming across multiple files #17827
-
Hi! I have a large theme. I am wondering what the best practices are when in comes to splitting this into multiple files. My single file @layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "./custom-preflight.css" layer(base);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css" layer(utilities);
:root {
--ds-gray-100: oklch(0.961 0 0);
}
.dark {
--ds-gray-100: oklch(0.218 0 0);
}
@theme inline {
--radius-*: initial;
--spacing: 0.25rem;
--animation-delay-0: 0ms;
--color-gray-100: var(--ds-gray-100);
}
--animate-loading: loading 8s ease-in-out infinite;
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
@utility text-heading-72 {
@apply font-sans text-[72px] leading-[72px] font-semibold tracking-[-4.32px];
} In my mental model, I should be able to split this up like this for example:
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
+ @import "./theme.css" layer(theme);
@import "./custom-preflight.css" layer(base);
@import "tailwindcss/preflight.css" layer(base);
+ @import "./animation.css" layer(base); // base layer?
@import "tailwindcss/utilities.css" layer(utilities);
+ @import "./utilities.css" layer(utilities);
:root {
--ds-gray-100: oklch(0.961 0 0);
}
.dark {
--ds-gray-100: oklch(0.218 0 0);
}
@theme inline {
--radius-*: initial;
--spacing: 0.25rem;
--animation-delay-0: 0ms;
--color-gray-100: var(--ds-gray-100);
}
@utility text-heading-72 {
@apply font-sans text-[72px] leading-[72px] font-semibold tracking-[-4.32px];
}
--animate-loading: loading 8s ease-in-out infinite;
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
The approach I settled on was to split my stylesheets according to tailwind's layer name and utility namespace. I found it to be the most minimal structure as of now, You don't really want to start out with too many separate files because it would make editing your styles a huge chore. Here's an example file structure, which you can expand on as needed (i.e. base.css, sources.css, etc...) styles/
colors.css
main.css # globals.css
spacing.css
typography.css
utilities.css Technically, you can start off with a theme.css file that contains colors, spacing, typography, animation, etc and break that up when it grows too large. For my use case, colors, spacing, and typography were the main theme edits I was making so I ended up with those three files. The colors stylesheet contains custom variables, variants for dark/light theme, a base layer for light/dark color schemes, and an inline theme to map them to color utility namespaces. I found it easier to contain all the colors together into a single file rather than having separate files for theme, base, etc. Here's an example with shadcn/ui colors: @layer properties {
/* Using oklch() if supported */
@supports (color: oklch(50% 0 0)) {
:root {
/* Theme Colors */
--cs-dark: oklch(0% 0 0); /* #000000 */
--cs-light: oklch(100% 0 0); /* #ffffff */
/* Dark Gray Colors */
--cs-smog-0: oklch(12% 0 0); /* #060606 */
--cs-smog-1: oklch(18% 0 0); /* #121212 */
--cs-smog-2: oklch(22% 0 0); /* #1b1b1b */
--cs-smog-3: oklch(26% 0 0); /* #242424 */
--cs-smog-4: oklch(30% 0 0); /* #2e2e2e */
--cs-smog-5: oklch(32% 0 0); /* #333333 */
--cs-smog-6: oklch(36% 0 0); /* #3d3d3d */
--cs-smog-7: oklch(40% 0 0); /* #484848 */
--cs-smog-8: oklch(44% 0 0); /* #525252 */
--cs-smog-9: oklch(48% 0 0); /* #5d5d5d */
--cs-smog-10: oklch(52% 0 0); /* #696969 */
/* Light Gray Colors */
--cs-fog-0: oklch(70% 0 0); /* #9e9e9e */
--cs-fog-1: oklch(74% 0 0); /* #ababab */
--cs-fog-2: oklch(77% 0 0); /* #b4b4b4 */
--cs-fog-3: oklch(80% 0 0); /* #bebebe */
--cs-fog-4: oklch(84% 0 0); /* #cacaca */
--cs-fog-5: oklch(87% 0 0); /* #d4d4d4 */
--cs-fog-6: oklch(90% 0 0); /* #dedede */
--cs-fog-7: oklch(92% 0 0); /* #e4e4e4 */
--cs-fog-8: oklch(94% 0 0); /* #ebebeb */
--cs-fog-9: oklch(96% 0 0); /* #f2f2f2 */
--cs-fog-10: oklch(98% 0 0); /* #f8f8f8 */
/* Primary Colors */
--cs-primary-0: oklch(40% 0.06 170); /* #215243 */
--cs-primary-1: oklch(45% 0.06 170); /* #2f6050 */
--cs-primary-2: oklch(50% 0.09 170); /* #1b735b */
--cs-primary-3: oklch(55% 0.09 170); /* #2e8269 */
--cs-primary-4: oklch(60% 0.09 170); /* #3f9278 */
--cs-primary-5: oklch(65% 0.09 170); /* #50a187 */
--cs-primary-6: oklch(70% 0.09 170); /* #60b196 */
--cs-primary-7: oklch(75% 0.09 170); /* #70c1a5 */
--cs-primary-8: oklch(80% 0.09 170); /* #80d1b5 */
--cs-primary-9: oklch(85% 0.09 170); /* #90e1c5 */
--cs-primary-10: oklch(90% 0.09 170); /* #a0f2d5 */
/* Secondary Colors */
--cs-secondary-0: oklch(15% 0.07 300); /* #110223 */
--cs-secondary-1: oklch(20% 0.07 300); /* #1b0c30 */
--cs-secondary-2: oklch(25% 0.07 300); /* #27183e */
--cs-secondary-3: oklch(30% 0.07 300); /* #33244b */
--cs-secondary-4: oklch(35% 0.07 300); /* #403159 */
--cs-secondary-5: oklch(40% 0.07 300); /* #4e3f68 */
--cs-secondary-6: oklch(45% 0.07 300); /* #5b4c76 */
--cs-secondary-7: oklch(50% 0.07 300); /* #695a85 */
--cs-secondary-8: oklch(55% 0.07 300); /* #786895 */
--cs-secondary-9: oklch(60% 0.07 300); /* #8677a4 */
--cs-secondary-10: oklch(65% 0.07 300); /* #9586b4 */
/* State & Status Colors */
--cs-accent: oklch(0.5 0.14 250); /* #0465af */
--cs-danger: oklch(0.5 0.18 22); /* #b32130 */
}
}
/* Fallback using hex values if oklch() not supported */
@supports not (color: oklch(50% 0 0)) {
:root {
/* Theme Colors */
--cs-dark: #000000;
--cs-light: #ffffff;
/* Dark Gray Colors */
--cs-smog-0: #060606;
--cs-smog-1: #121212;
--cs-smog-2: #1b1b1b;
--cs-smog-3: #242424;
--cs-smog-4: #2e2e2e;
--cs-smog-5: #383838;
--cs-smog-6: #424242;
--cs-smog-7: #4d4d4d;
--cs-smog-8: #585858;
--cs-smog-9: #636363;
--cs-smog-10: #6f6f6f;
/* Light Gray Colors */
--cs-fog-0: #9e9e9e;
--cs-fog-1: #ababab;
--cs-fog-2: #b4b4b4;
--cs-fog-3: #bebebe;
--cs-fog-4: #cacaca;
--cs-fog-5: #d4d4d4;
--cs-fog-6: #dedede;
--cs-fog-7: #e4e4e4;
--cs-fog-8: #ebebeb;
--cs-fog-9: #f2f2f2;
--cs-fog-10: #f8f8f8;
/* Primary Colors */
--cs-primary-0: #215243;
--cs-primary-1: #2f6050;
--cs-primary-2: #1b735b;
--cs-primary-3: #2e8269;
--cs-primary-4: #3f9278;
--cs-primary-5: #50a187;
--cs-primary-6: #60b196;
--cs-primary-7: #70c1a5;
--cs-primary-8: #80d1b5;
--cs-primary-9: #90e1c5;
--cs-primary-10: #a0f2d5;
/* Secondary Colors */
--cs-secondary-0: #110223;
--cs-secondary-1: #1b0c30;
--cs-secondary-2: #27183e;
--cs-secondary-3: #33244b;
--cs-secondary-4: #403159;
--cs-secondary-5: #4e3f68;
--cs-secondary-6: #5b4c76;
--cs-secondary-7: #695a85;
--cs-secondary-8: #786895;
--cs-secondary-9: #8677a4;
--cs-secondary-10: #9586b4;
/* State & Status Colors */
--cs-accent: #0465af;
--cs-danger: #b32130;
}
}
}
@variant dark (&:is(.dark, [data-theme="dark"] *));
@variant light (&:is(.light, [data-theme="light"] *));
@theme inline {
/* Theme Colors */
--color-dark: var(--cs-dark);
--color-light: var(--cs-light);
/* UI Colors */
--color-background: var(--c-background);
--color-foreground: var(--c-foreground);
--color-input: var(--c-input);
--color-input-foreground: var(--c-input-foreground);
--color-border: var(--c-border);
--color-ring: var(--c-ring);
--color-card: var(--c-card);
--color-card-foreground: var(--c-card-foreground);
--color-popover: var(--c-popover);
--color-popover-foreground: var(--c-popover-foreground);
--color-muted: var(--c-muted);
--color-muted-foreground: var(--c-muted-foreground);
/* Semantic Colors */
--color-neutral: var(--c-neutral);
--color-neutral-foreground: var(--c-neutral-foreground);
--color-primary: var(--c-primary);
--color-primary-foreground: var(--c-primary-foreground);
--color-secondary: var(--c-secondary);
--color-secondary-foreground: var(--c-secondary-foreground);
/* State & Status Colors */
--color-accent: var(--c-accent);
--color-accent-foreground: var(--c-accent-foreground);
--color-danger: var(--c-danger);
--color-danger-foreground: var(--c-danger-foreground);
}
@layer base {
/* Light Theme Colors */
:root,
.light,
[data-theme='light'] {
color-scheme: light;
/* UI Colors */
--c-background: var(--cs-fog-9);
--c-foreground: var(--cs-smog-2);
--c-input: var(--cs-fog-10);
--c-input-foreground: var(--cs-smog-0);
--c-border: var(--cs-fog-5);
--c-ring: var(--cs-fog-0);
--c-card: var(--cs-fog-8);
--c-card-foreground: var(--cs-smog-3);
--c-popover: var(--cs-fog-7);
--c-popover-foreground: var(--cs-smog-4);
--c-muted: var(--cs-fog-6);
--c-muted-foreground: var(--cs-smog-5);
/* Semantic Colors */
--c-neutral: var(--cs-dark);
--c-neutral-foreground: var(--cs-light);
--c-primary: var(--cs-primary-2);
--c-primary-foreground: var(--cs-light);
--c-secondary: var(--cs-secondary-3);
--c-secondary-foreground: var(--cs-light);
/* State & Status Colors */
--c-accent: var(--cs-accent);
--c-accent-foreground: var(--cs-light);
--c-danger: var(--cs-danger);
--c-danger-foreground: var(--cs-light);
}
/* Dark Theme Colors */
.dark,
[data-theme='dark'] {
color-scheme: dark;
/* UI Colors */
--c-background: var(--cs-smog-2);
--c-foreground: var(--cs-fog-8);
--c-input: var(--cs-fog-10);
--c-input-foreground: var(--cs-smog-0);
--c-border: var(--cs-smog-10);
--c-ring: var(--cs-fog-0);
--c-card: var(--cs-smog-3);
--c-card-foreground: var(--cs-fog-8);
--c-popover: var(--cs-smog-4);
--c-popover-foreground: var(--cs-fog-7);
--c-muted: var(--cs-smog-5);
--c-muted-foreground: var(--cs-fog-6);
/* Semantic Colors */
--c-neutral: var(--cs-light);
--c-neutral-foreground: var(--cs-dark);
--c-primary: var(--cs-primary-9);
--c-primary-foreground: var(--cs-dark);
--c-secondary: var(--cs-secondary-10);
--c-secondary-foreground: var(--cs-dark);
/* State & Status Colors */
--c-accent: var(--cs-accent);
--c-accent-foreground: var(--cs-light);
--c-danger: var(--cs-danger);
--c-danger-foreground: var(--cs-light);
}
} Also, since your stylesheets already have |
Beta Was this translation helpful? Give feedback.
-
Yep this looks like it should work to me — is there any specific thing you're trying that doesn't work? Don't really have any "best practices" per se, can really just split things up however you like. You should be able to simplify what you have though to this: @import "tailwindcss";
@import "./theme.css" layer(theme);
@import "./custom-preflight.css" layer(base);
@import "./animation.css" layer(base);
@import "./utilities.css" layer(utilities);
:root {
--ds-gray-100: oklch(0.961 0 0);
}
.dark {
--ds-gray-100: oklch(0.218 0 0);
} Our main CSS export will define all the layers and stuff for you 👍 |
Beta Was this translation helpful? Give feedback.
Yep this looks like it should work to me — is there any specific thing you're trying that doesn't work?
Don't really have any "best practices" per se, can really just split things up however you like.
You should be able to simplify what you have though to this:
Our main CSS export will define all the layers and stuff for you 👍