Nuxt and Windi, Can I get current theme in windi.config.ts?

289 Views Asked by At

I created a theme.ts where I'm deteting the site "brand" by the hostname (based in the awesome starter nuxt3 repository versoin) like so:

utils/theme.ts

export type IThemeSettingOptions = 'light' | 'system' | 'foo' | 'bar'

export type ITheme = 'light' | 'foo' | 'bar'

export const availableThemes: {
  key: IThemeSettingOptions
  text: string
}[] = [
    { key: 'light', text: 'Light' },
    { key: 'system', text: 'System' },
    { key: 'foo', text: 'Foo' },
    { key: 'bar', text: 'Bar' },
  ]

export function ThemeManager() {
  // composable
  const themeUserSetting = useCookie<IThemeSettingOptions>('theme')

  // methods
  const getUserSetting = (): IThemeSettingOptions => {
    return themeUserSetting.value || 'system'
  }

  const getSiteTheme = (): ITheme => {
    try {
      const host = location.hostname
      const sites = {
        'foo.whitelabel.com': 'foo',
        'bar.whitelabel.com': 'bar'
      }

      if (sites[host]) {
        return sites[host]
      }
    } catch (e) {
      return 'light' // This means generic styles, no customizable brand
    }
  }

  // state
  const themeSetting = useState<IThemeSettingOptions>('theme.setting', () =>
    getSiteTheme()
  )

  const themeCurrent = useState<ITheme>('theme.current', () =>
    process.client ? getSiteTheme() : 'light'
  )

  // init theme
  const init = () => {
    themeSetting.value = getSiteTheme()
  }

  // lifecycle
  onBeforeMount(() => init())

  onMounted(() => {
    themeCurrent.value = getSiteTheme()
  })

  return {
    themeSetting,
    themeCurrent,
    getUserSetting,
    getSiteTheme
  }
}

I know of this color mode that nuxt offers. But As I'm using windicss I would like to have a different config file/object for every theme/brand.

I tried like this in the windi.config.ts

import { defineConfig } from 'windicss/helpers';
import type { Plugin } from 'windicss/types/interfaces';
import { ThemeManager } from './utils/theme';

console.log(ThemeManager().themeCurrent)
// etc..

But this won't log anything in the console

is this posible? if not, a similar workaround? (perhaps with tailwindcss)

I want to do this so the primary color is different for foo than for bar brand, lets say something like this:

windi.config.ts

const MyThemes = {
  light: {
    colors: {
      green: {
        DEFAULT: '#3BA670'
      },
      blue: {
        DEFAULT: '#0096F0'
      }
    }
  },
  foo: {
    colors: {
      green: {
        DEFAULT: '#3BA675'
      },
      blue: {
        DEFAULT: '#0096F5'
      }
    }
  },
  bar: {
    colors: {
      green: {
        DEFAULT: '#3BA67F'
      },
      blue: {
        DEFAULT: '#0096FF'
      }
    }
  }
}

const MyTheme = MyThemes['foo'] // or 'bar' or 'light'


export default defineConfig({
  // etc..
  theme: {
    extend: {
      colors: {
        primary: MyTheme.colors.green,
        // etc.. 
      }
    },
  }
  // etc..
})
2

There are 2 best solutions below

2
kissu On
  1. If you want to get the actual value in JS of a specific token in Tailwind, you can use the following as shown in this answer.
import resolveConfig from 'tailwindcss/resolveConfig'
import tailwindConfig from '@/tailwind.config.js'
const twFullConfig = resolveConfig(tailwindConfig)

...
mounted() {
  console.log('tw', twFullConfig.theme.colors['primary-500'])
}
  1. And if you want to have something dynamic depending of a specific variable, you need to map it to an object and you'll get your classes as shown here. This is quite handy if you want something dynamic while using an utility-based CSS framework with a proper design system.

  2. Tailwind does not allow dynamic classes for performance reasons, hence why such mapping is a necessary evil.

UnoCSS is probably the most flexible solution that I know but it's still not adapted to solve an issue like that. Neither is the usage of using some CSS variables and overwriting them on the fly since a design system is based on far more things than just a color or a size.

TLDR: theming is hard and requires quite some hand-made mapping to be done efficiently. I've tried that at a client and it's complex quite early, especially if you think about a way of doing that dynamically.

I've tried that one and twind's shim. It's feasible to have it on runtime (if needed) but quite tricky for sure.

0
ryangus On

If you can rely on CSS vars (Custom Properties) IMHO you could simplify this.

  1. Set a class on the html based on theme, for instance <html class="dark">...</html> or <html class="foo">...</html>. You can accomplish this in the nuxt.config.ts app section or the color module you mentioned.
  app: {
    // https://nuxt.com/docs/api/configuration/nuxt-config#head
    head: {
      htmlAttrs: {
        lang: 'en',
        class: 'foo', // Here you could an environment variable
      },
    },
  },
  1. Set colors on a stylesheet.
// theme.css

:root {
  --color-primary: #3BA670; /* or Whatever value you like, HSL, RGBA, colornames, ecc */
  --color-error: darkred;
}

html.foo {
  --color-primary: #3BA675;
  --color-error: mediumvioletred;
}

html.bar {
  --color-primary: orange;
  --color-error: indianred;
}

Finally in your windi.config.ts

export default defineConfig({
  // etc..
  theme: {
    extend: {
      colors: {
        primary: 'var(--color-primary)',
        error: 'var(--color-error)',
        // etc.. 
      }
    },
  }
  // etc..
})

Also, if you have a solid color system you can omit extending colors and just overide the windi palette and implement yours.