Skip to content

Instantly share code, notes, and snippets.

@Chizaruu
Last active October 2, 2024 16:01
Show Gist options
  • Select an option

  • Save Chizaruu/8353e224b386c7ac69929019969bdb00 to your computer and use it in GitHub Desktop.

Select an option

Save Chizaruu/8353e224b386c7ac69929019969bdb00 to your computer and use it in GitHub Desktop.
Set your color schemes using server-side environment variables for effortless, dynamic theming in Svelte Tailwind apps.

Hey there! I've developed a practical method for managing color schemes in Svelte applications using server-side environment variables. This approach offers a streamlined way to dynamically alter an app's visual theme without modifying the codebase. Here's how it works:

  • Color schemes are defined in the Tailwind configuration file.
  • A custom Node.js script converts these schemes into CSS custom properties.
  • The desired color scheme is set via a server-side environment variable.
  • The application applies the selected scheme using CSS and Svelte's server-side rendering.

Key features:

  • Allows theme switching without client-side JavaScript.
  • Integrates smoothly with server-side rendering, preventing style flashes on load.
  • Simplifies the process of creating multiple branded versions of an application.
  • Enables easy implementation of seasonal themes or A/B testing of color schemes.

This method is particularly useful for projects requiring different branded looks or frequent style changes. It keeps theming logic separate from application code, resulting in cleaner, more maintainable projects. If you're working on applications that need flexible theming options, this approach could be a valuable addition to your toolkit.

import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async () => {
const colorScheme = process.env.COLOR_SCHEME || 'flowbite';
return { colorScheme };
};
//Your main +layout.svelte file
<script lang="ts">
import '../app.pcss';
export let data;
let { colorScheme } = data;
</script>
<svelte:head>
<meta name="color-scheme" content={colorScheme} />
</svelte:head>
<div data-color-scheme={colorScheme}>
<slot />
</div>
<script lang="ts">
import { page } from '$app/stores';
$: colorScheme = $page.data.colorScheme;
</script>
<div class="container mx-auto p-4">
<h1 class="mb-6 text-4xl font-bold text-primary-700">Color Scheme Demo</h1>
<div class="mb-6">
<h2 class="mb-2 text-2xl font-semibold text-primary-600">Current Scheme: {colorScheme}</h2>
<p class="text-primary-800">The color scheme is set via the .env file.</p>
</div>
<div class="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-5">
{#each [50, 100, 200, 300, 400, 500, 600, 700, 800, 900] as shade}
<div
class="flex h-24 items-center justify-center rounded-lg p-2 text-center bg-primary-{shade}"
>
<span class="font-semibold {shade >= 500 ? 'text-primary-50' : 'text-primary-900'}">
Primary {shade}
</span>
</div>
{/each}
</div>
</div>
const fs = require('fs');
const tailwindConfig = require('./tailwind.config.cjs');
const colorSchemes = tailwindConfig.colorSchemes;
async function updateAppPcss() {
const inputFile = 'src/app.pcss';
try {
let originalCss = fs.readFileSync(inputFile, 'utf8');
// Extract Tailwind directives
const tailwindDirectives = originalCss.match(/@tailwind [^;]+;/g) || [];
// Add CSS custom properties for each color scheme
const colorSchemesCss = Object.entries(colorSchemes)
.map(([schemeName, schemeColors]) => {
const schemeVars = Object.entries(schemeColors)
.map(([shade, color]) => ` --color-primary-${shade}: ${color};`)
.join('\n');
return `[data-color-scheme="${schemeName}"] {\n${schemeVars}\n}`;
})
.join('\n\n');
// Combine everything
const finalCss = [...tailwindDirectives, '', colorSchemesCss].join('\n');
// Write the updated CSS back to app.pcss
fs.writeFileSync(inputFile, finalCss);
console.log(
`Updated ${inputFile} with processed CSS and all color schemes, preserving Tailwind directives`
);
} catch (error) {
console.error(`Error updating ${inputFile}:`, error);
process.exit(1);
}
}
updateAppPcss()
.then(() => {
console.log('CSS update process completed');
})
.catch((error) => {
console.error('CSS update process failed:', error);
process.exit(1);
});
//Modify the scripts to make sure it uses the build-css file
{
"scripts": {
"build:css": "node build-css.cjs",
"dev": "npm run build:css && vite dev",
"build": "npm run build:css && vite build",
}
}
const defaultTheme = require('tailwindcss/defaultTheme');
const colorSchemes = {
flowbite: {
50: '#FFF5F2',
100: '#FFF1EE',
200: '#FFE4DE',
300: '#FFD5CC',
400: '#FFBCAD',
500: '#FE795D',
600: '#EF562F',
700: '#EB4F27',
800: '#CC4522',
900: '#A5371B'
},
rose: {
50: '#fff1f2',
100: '#ffe4e6',
200: '#fecdd3',
300: '#fda4af',
400: '#fb7185',
500: '#f43f5e',
600: '#e11d48',
700: '#be123c',
800: '#9f1239',
900: '#881337'
},
pink: {
50: '#fdf2f8',
100: '#fce7f3',
200: '#fbcfe8',
300: '#f9a8d4',
400: '#f472b6',
500: '#ec4899',
600: '#db2777',
700: '#be185d',
800: '#9d174d',
900: '#831843'
},
fuchsia: {
50: '#fdf4ff',
100: '#fae8ff',
200: '#f5d0fe',
300: '#f0abfc',
400: '#e879f9',
500: '#d946ef',
600: '#c026d3',
700: '#a21caf',
800: '#86198f',
900: '#701a75'
},
purple: {
50: '#faf5ff',
100: '#f3e8ff',
200: '#e9d5ff',
300: '#d8b4fe',
400: '#c084fc',
500: '#a855f7',
600: '#9333ea',
700: '#7e22ce',
800: '#6b21a8',
900: '#581c87'
},
violet: {
50: '#f5f3ff',
100: '#ede9fe',
200: '#ddd6fe',
300: '#c4b5fd',
400: '#a78bfa',
500: '#8b5cf6',
600: '#7c3aed',
700: '#6d28d9',
800: '#5b21b6',
900: '#4c1d95'
},
indigo: {
50: '#eef2ff',
100: '#e0e7ff',
200: '#c7d2fe',
300: '#a5b4fc',
400: '#818cf8',
500: '#6366f1',
600: '#4f46e5',
700: '#4338ca',
800: '#3730a3',
900: '#312e81'
},
blue: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a'
},
sky: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e'
},
cyan: {
50: '#ecfeff',
100: '#cffafe',
200: '#a5f3fc',
300: '#67e8f9',
400: '#22d3ee',
500: '#06b6d4',
600: '#0891b2',
700: '#0e7490',
800: '#155e75',
900: '#164e63'
},
teal: {
50: '#f0fdfa',
100: '#ccfbf1',
200: '#99f6e4',
300: '#5eead4',
400: '#2dd4bf',
500: '#14b8a6',
600: '#0d9488',
700: '#0f766e',
800: '#115e59',
900: '#134e4a'
},
emerald: {
50: '#ecfdf5',
100: '#d1fae5',
200: '#a7f3d0',
300: '#6ee7b7',
400: '#34d399',
500: '#10b981',
600: '#059669',
700: '#047857',
800: '#065f46',
900: '#064e3b'
},
green: {
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#14532d'
},
lime: {
50: '#f7fee7',
100: '#ecfccb',
200: '#d9f99d',
300: '#bef264',
400: '#a3e635',
500: '#84cc16',
600: '#65a30d',
700: '#4d7c0f',
800: '#3f6212',
900: '#365314'
},
yellow: {
50: '#fefce8',
100: '#fef9c3',
200: '#fef08a',
300: '#fde047',
400: '#facc15',
500: '#eab308',
600: '#ca8a04',
700: '#a16207',
800: '#854d0e',
900: '#713f12'
},
amber: {
50: '#fffbeb',
100: '#fef3c7',
200: '#fde68a',
300: '#fcd34d',
400: '#fbbf24',
500: '#f59e0b',
600: '#d97706',
700: '#b45309',
800: '#92400e',
900: '#78350f'
},
orange: {
50: '#fff7ed',
100: '#ffedd5',
200: '#fed7aa',
300: '#fdba74',
400: '#fb923c',
500: '#f97316',
600: '#ea580c',
700: '#c2410c',
800: '#9a3412',
900: '#7c2d12'
},
red: {
50: '#fef2f2',
100: '#fee2e2',
200: '#fecaca',
300: '#fca5a5',
400: '#f87171',
500: '#ef4444',
600: '#dc2626',
700: '#b91c1c',
800: '#991b1b',
900: '#7f1d1d'
},
stone: {
50: '#fafaf9',
100: '#f5f5f4',
200: '#e7e5e4',
300: '#d6d3d1',
400: '#a8a29e',
500: '#78716c',
600: '#57534e',
700: '#44403c',
800: '#292524',
900: '#1c1917'
},
neutral: {
50: '#fafafa',
100: '#f5f5f5',
200: '#e5e5e5',
300: '#d4d4d4',
400: '#a3a3a3',
500: '#737373',
600: '#525252',
700: '#404040',
800: '#262626',
900: '#171717'
},
zinc: {
50: '#fafafa',
100: '#f4f4f5',
200: '#e4e4e7',
300: '#d4d4d8',
400: '#a1a1aa',
500: '#71717a',
600: '#52525b',
700: '#3f3f46',
800: '#27272a',
900: '#18181b'
},
gray: {
50: '#f9fafb',
100: '#f3f4f6',
200: '#e5e7eb',
300: '#d1d5db',
400: '#9ca3af',
500: '#6b7280',
600: '#4b5563',
700: '#374151',
800: '#1f2937',
900: '#111827'
},
slate: {
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#334155',
800: '#1e293b',
900: '#0f172a'
}
};
const config = {
content: [
'./src/**/*.{html,js,svelte,ts,md}'
],
theme: {
fontFamily: {
sans: ['Inter', ...defaultTheme.fontFamily.sans]
},
extend: {
colors: {
primary: {
50: 'var(--color-primary-50)',
100: 'var(--color-primary-100)',
200: 'var(--color-primary-200)',
300: 'var(--color-primary-300)',
400: 'var(--color-primary-400)',
500: 'var(--color-primary-500)',
600: 'var(--color-primary-600)',
700: 'var(--color-primary-700)',
800: 'var(--color-primary-800)',
900: 'var(--color-primary-900)'
}
}
}
},
darkMode: 'class'
};
module.exports = { ...config, colorSchemes };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment