cssnext.jscolorsenvironment-variablescss-variables

How do I use environnement variable in my CSS using nextJs to specify base color?


how can I use an environment variable to specify the base color in my CSS ?

I use NEXTJS

My colors are all defined as variables in my globals.css :

@layer base {
   :root {
      ...
      --primary: #0096d0;
      --primary-bright: hsl(from var(--primary) h s calc(l*1.5));
      ...

I tried

--primary: env(NEXT_PUBLIC_PRIMARY_COLOR, #0096d0);

But the env variable is ignored

Any idea ?


Solution

  • While env() does work with custom properties, it reads from UA-defined environment variables, not from the published NEXT_PUBLIC_ variables in the client-side, and definetly not server-side environment variables. Though in the future we might be able to add custom environment variables via JS or CSS, currently you can't define client-side Custom CSS Environment Variables yet.

    Hence, for now you will need to either consider using CSS preprocessors, inline-styles in JSX, or PostCSS plugins. They all have their own drawbacks.

    Inline Styles

    The main disadvantage is that this can't be used in the global.css like you were. However, the advantage is that you can create the style object however you want. You can also update the style object and trigger an update after the app is running. (Note that all NEXT_PUBLIC_ variables will be frozen from being updated during runtime.)

    In index.js:

    //this can be created dynamically by looping over keys in .env.local
    const style = {"--primary": process.env.NEXT_PUBLIC_PRIMARY_COLOR ?? '#0096d0'}
    <div style={style}>
        <MyRoot></MyRoot>
    </div>
    

    CSS Preprocessors

    One way is to import specific environment variables into SCSS variables using the SASS additional-data loader option.

    For Next, you can follow the Next document Styling with SCSS to install SASS:

    npm install --save-dev sass
    

    In next.config.js, prepend the SASS variables:

    const nextConfig = {
      sassOptions: {
        additionalData: Object.keys(process.env).reduce((accumulator, currentValue) => {
          if (currentValue.startsWith('NEXT_THEME_')) {
            return `${accumulator}$${currentValue}: ${process.env[currentValue]};`
          }
          else {
            return accumulator
          }
        }, ''),
        includePaths: [path.join(__dirname, 'styles')],
      }
    }
    

    In .env.local:

    NEXT_THEME_PRIMARY_COLOR=red
    NEXT_THEME_SECONDARY_COLOR='#fff'
    

    Then you can use the environment variables in globals.scss by:

    @layer base {
      :root {
        --primary: #{$NEXT_THEME_PRIMARY_COLOR};
      }
    }
    

    The drawback is that SCSS is "preprocessed", means that the SCSS variables also don't exist for you to update during JS runtime. Also, the environment variable have to always be presented, or else you need to use variable-exists() or !default explicitly, which is somewhat verbose. For example:

    :root {
      --primary: #0096d0;
      @if variable-exists(NEXT_THEME_PRIMARY_COLOR) {
        --primary: #{$NEXT_THEME_PRIMARY_COLOR};
      }
    }
    

    PostCSS Plugins

    Since you are using Nextjs, PostCSS and some plugins are built-in already. However, currently none of the built-in ones matches your need. You need to find a plugin or implement one that solves your problem.

    One example is the postcss-functions plugin that allows you define your own env function that reads from environment variables.

    In postcss.config.js:

    const theme = Object.keys(process.env).reduce((accumulator, currentValue) => {
      if (currentValue.startsWith('NEXT_THEME_')) {
        accumulator[currentValue] = process.env[currentValue];
        return accumulator;
      }
      else {
        return accumulator
      }
    }, {})
    
    function getEnv(variable, fallback = null) {
      return theme[variable] ?? fallback;
    }
    
    module.exports = {
      "plugins": {
        "postcss-functions": {
          "functions": {
            getEnv
          }
        },
      }
    }
    

    Then you can use it in global.css as follow:

    @layer base {
      :root {
        --primary: getEnv(NEXT_THEME_PRIMARY_COLOR, #0096d0);
      }
    }