Skip to content

Customizing Xpulse

Xpulse relies on Tailwind CSS, streamlining the customization process. Here's a guide on adapting the product to meet your specific needs.

Understanding Tailwind CSS

Tailwind CSS is at the core of our theme/template's styling. It simplifies styling with a utility-first approach, using pre-defined CSS classes directly in React/JSX. Key aspects:

  • Utility Classes: Apply classes like bg-blue-500 to change background color to a shade of blue, py-5 to add vertical padding of 1.25rem and more.
  • Customization: Tailor the framework through the tailwind.config.js file for colors, fonts, and more.
  • Responsive Design: Use responsive classes for adaptable layouts.
  • Component Styling: Apply classes to customize individual components.
  • Advantages: Enhance productivity, ensure consistent styling, and prototype rapidly.
  • Documentation: Refer to the official Tailwind CSS documentation for more details.

Tailwind Configuration

Tailoring the configuration to your preferences is possible by accessing the tailwind.config.js file located in the project directory. This file grants you the capability to personalize elements like colors, fonts, spacing, and additional settings to align with your specific requirements.

read more on tailwind customization

Projects Tailwind Configuration File

tailwind.config.js

js
const plugin = require('tailwindcss/plugin')
const colors = require('tailwindcss/colors')

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/pages/**/*.{js,ts,jsx,tsx}', './src/layouts/**/*.{js,ts,jsx,tsx}', './src/components/**/*.{js,ts,jsx,tsx}', './src/documentation/**/*.{js,ts,jsx,tsx}'],
  darkMode: 'class',

  theme: {
    container: {
      center: true,
    },
    extend: {
      screens: {
        xs: { max: '639px' },
        sm: '640px',
        md: '768px',
        lg: '1025px',
        xl: '1280px',
        xxl: '1536px',
        ptablet: {
          raw: '(min-width: 768px) and (max-width: 1024px) and (orientation: portrait)',
        },
        ltablet: {
          raw: '(min-width: 768px) and (max-width: 1024px) and (orientation: landscape)',
        },
      },
      fontFamily: {
        sans: ['var(--font-inter)'],
      },
      colors: {
        muted: {
          ...colors.slate,
        },
        primary: colors.indigo,
        info: colors.sky,
        success: colors.teal,
        warning: colors.amber,
        danger: colors.rose,
      },
    },

    keyframes: {
      indeterminate: {
        '0%': { 'margin-left': '-10%' },
        '100%': { 'margin-left': '100%' },
      },
      'circle-chart-fill': {
        to: {
          'stroke-dasharray': '0 100',
        },
      },
      wave: {
        '0%': {
          transform: 'scale(1)',
          opacity: '1',
        },

        '25%': {
          transform: 'scale(1)',
          opacity: '1',
        },

        '100%': {
          transform: 'scale(4.5)',
          opacity: '0',
        },
      },
      fadeInUp: {
        from: {
          transform: 'translate3d(0, 20px, 0)',
        },

        to: {
          transform: 'translate3d(0, 0, 0)',
          opacity: 1,
        },
      },
      fadeInLeft: {
        from: {
          transform: 'translate3d(20px, 0, 0)',
          opacity: '0',
        },
        to: {
          transform: 'translate3d(0, 0, 0)',
          opacity: '1',
        },
      },
      spinAround: {
        from: {
          transform: 'rotate(0deg)',
        },

        to: {
          transform: 'rotate(359deg)',
        },
      },
    },
    animation: {
      'spin-slow': 'spin 3s linear infinite',
      'spin-fast': 'spin 0.65s linear infinite',
      indeterminate: 'indeterminate 1s cubic-bezier(0.4, 0, 0.2, 1) infinite',
    },
  },

  plugins: [
    plugin(({ addComponents, addVariant }) => {
      //target progress container
      addVariant('progress-container', '&::-webkit-progress-bar')

      //target progress bar/inner
      addVariant('progress-bar', ['&::-webkit-progress-value', '&::-moz-progress-bar', '&::-ms-fill'])

      addComponents({
        '.slimscroll::-webkit-scrollbar': {
          width: '6px',
        },
        '.slimscroll::-webkit-scrollbar-thumb': {
          'border-radius': '.75rem',
          background: 'rgba(0, 0, 0, 0.1)',
        },
        '.slimscroll-opaque::-webkit-scrollbar-thumb': {
          background: 'rgba(0, 0, 0, 0) !important',
        },
        '.mask': {
          'mask-size': 'contain',
          'mask-repeat': 'no-repeat',
          'mask-position': 'center',
        },
        '.mask-hex': {
          'mask-image':
            "url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE4MiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNjQuNzg2IDE4MS40Yy05LjE5NiAwLTIwLjA2My02LjY4Ny0yNS4wNzktMTQuMjFMMy43NjIgMTA1LjMzYy01LjAxNi04LjM2LTUuMDE2LTIwLjkgMC0yOS4yNTlsMzUuOTQ1LTYxLjg2QzQ0LjcyMyA1Ljg1MSA1NS41OSAwIDY0Ljc4NiAwaDcxLjA1NWM5LjE5NiAwIDIwLjA2MyA2LjY4OCAyNS4wNzkgMTQuMjExbDM1Ljk0NSA2MS44NmM0LjE4IDguMzYgNC4xOCAyMC44OTkgMCAyOS4yNThsLTM1Ljk0NSA2MS44NmMtNC4xOCA4LjM2LTE1Ljg4MyAxNC4yMTEtMjUuMDc5IDE0LjIxMUg2NC43ODZ6Ii8+PC9zdmc+')",
        },
        '.mask-hexed': {
          'mask-image':
            "url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgyIiBoZWlnaHQ9IjIwMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNLjMgNjUuNDg2YzAtOS4xOTYgNi42ODctMjAuMDYzIDE0LjIxMS0yNS4wNzhsNjEuODYtMzUuOTQ2YzguMzYtNS4wMTYgMjAuODk5LTUuMDE2IDI5LjI1OCAwbDYxLjg2IDM1Ljk0NmM4LjM2IDUuMDE1IDE0LjIxMSAxNS44ODIgMTQuMjExIDI1LjA3OHY3MS4wNTVjMCA5LjE5Ni02LjY4NyAyMC4wNjMtMTQuMjExIDI1LjA3OWwtNjEuODYgMzUuOTQ1Yy04LjM2IDQuMTgtMjAuODk5IDQuMTgtMjkuMjU4IDBsLTYxLjg2LTM1Ljk0NUM2LjE1MSAxNTcuNDQuMyAxNDUuNzM3LjMgMTM2LjU0VjY1LjQ4NnoiLz48L3N2Zz4=')",
        },
        '.mask-deca': {
          'mask-image':
            "url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTkyIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNOTYgMGw1OC43NzkgMTkuMDk4IDM2LjMyNyA1MHY2MS44MDRsLTM2LjMyNyA1MEw5NiAyMDBsLTU4Ljc3OS0xOS4wOTgtMzYuMzI3LTUwVjY5LjA5OGwzNi4zMjctNTB6IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=')",
        },
        '.mask-blob': {
          'mask-image':
            "url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTAwIDBDMjAgMCAwIDIwIDAgMTAwczIwIDEwMCAxMDAgMTAwIDEwMC0yMCAxMDAtMTAwUzE4MCAwIDEwMCAweiIvPjwvc3ZnPg==')",
        },
        '.mask-diamond': {
          'mask-image':
            "url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTAwIDBsMTAwIDEwMC0xMDAgMTAwTDAgMTAweiIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9zdmc+')",
        },
      })
    }),
    function ({ addBase, theme }) {
      function extractColorVars(colorObj, colorGroup = '') {
        return Object.keys(colorObj).reduce((vars, colorKey) => {
          const value = colorObj[colorKey]

          const newVars = typeof value === 'string' ? { [`--color${colorGroup}-${colorKey}`]: value } : extractColorVars(value, `-${colorKey}`)

          return { ...vars, ...newVars }
        }, {})
      }

      addBase({
        ':root': extractColorVars(theme('colors')),
      })
    },
  ],
}

This theme/template comes with built-in support for both ☀️ light mode and 🌙 dark mode. This functionality is achieved through the utilization of dark variants to apply the appropriate classes for dark mode.

Configuration imports

The tailwind configuration file starts with commonjs (using require instead of import) import statements:

javascript
const colors = require('tailwindcss/colors')
const plugin = require('tailwindcss/plugin')

We are doing 2 things:

  • We first load the colors object from tailwind to be able to access the native colors later in the configuration file
  • We then load the plugin object to be able to use tailwind plugins later in the file.

Dark mode & Content

We then take care of configuring the Dark mode as well as the files to parse, looking for tailwind class names to compile in the final bundle:

javascript
module.exports = {
  content: ['./src/pages/**/*.{js,ts,jsx,tsx}', './src/layouts/**/*.{js,ts,jsx,tsx}', './src/components/**/*.{js,ts,jsx,tsx}', './src/documentation/**/*.{js,ts,jsx,tsx}'],
  darkMode: 'class',
  // ...rest of config
}

We are doing 2 things:

  • We are first telling tailwind to use the class implementation of the dark mode. This means that when the dark mode is toggled, a .dark class is added to the html DOM element, affecting all the application styles and colors. See Tailwind docs for more details about dark mode.
html
<!--Dark mode disabled-->
<html></html>

<!--Dark mode enabled-->
<html class="dark"></html>
  • We then tell tailwind to look for classes to compile inside all files residing inside the project folders, including files ending with ts, tsx, jsx and js extensions. Learn more about content parsing by taking a look at the Tailwind docs.

Extending the base theme

The tailwind configuration file provides a very convenient way of overwriting the default theme and set your own variables. Everything related to theme customization goes inside the extend theme section of the configuration file:

javascript
module.exports = {
  // ...previous config elements
  theme: {
    extend: {
      // The theme configuration goes here.
    },
  },
  // ...rest of config
}

There are several things we can change in there. If you want more details about the tailwind configuration, you can always read the dedicated section of the Tailwind docs. Let's get back to Xpulse and deep dive in each subsection of the theme configuration.

Breakpoints

In Tailwind CSS, responsive breakpoints are known as screens. Being a mobile first framework, screens are set in an ascending manner, so you always design mobile first. Here is the default Tailwind configuration:

javascript
module.exports = {
  // ...previous config elements
  theme: {
    extend: {
      screens: {
        sm: '640px',
        md: '768px',
        lg: '1024px',
        xl: '1280px',
        xxl: '1536px',
      },
    },
  },
  // ...rest of config
}

While the defaults are pretty satisfying, we though that we could tweak them a little bit to get the most out of it, and to be able to face those very particular edge cases that come up from time to time. Here is Xpulse's screens configuration:

javascript
module.exports = {
  // ...previous config elements
  theme: {
    extend: {
      screens: {
        xs: { max: '639px' },
        sm: '640px',
        md: '768px',
        lg: '1025px',
        xl: '1280px',
        xxl: '1536px',
        ptablet: {
          raw: '(min-width: 768px) and (max-width: 1024px) and (orientation: portrait)',
        },
        ltablet: {
          raw: '(min-width: 768px) and (max-width: 1024px) and (orientation: landscape)',
        },
      },
    },
  },
  // ...rest of config
}

Let's go through the changes we've made. We've added an xs screen that, opposed to the others that follow an ascending pattern, is capped to 639px to face some very rare edge cases where you'd like to have something only applied on very small screens. We've also decided to push the lg breakpoint to 1025px instead of 1024px to exclude landscape tablets from desktop display, simply because there is not as much room on a landscape tablet as there is on a desktop screen, implying therefore layout adjustments between the two. And finally, we've added two raw screens called ptblet (stands for portrait tablet) and ltablet (stands for landscape tablet) to handle specific layouts rendered in both orientations at maximum design quality.

Colors

Tailwind CSS provides a very advanced and fully customizable color system. If you don't know anything about Tailwind colors, we recommend that you take a look at . the Tailwind docs to learn more about them. In Xpulse, we use the theme section of the configuration file to configure our own colors, that we will be using in the template. It also means, that you can do the same thing to change all the template colors to match your branding. Here is the color configuration of Xpulse:

javascript
module.exports = {
  // ...previous config elements
  theme: {
    extend: {
      colors: {
        muted: {
          ...colors.slate,
        },
        primary: colors.indigo,
        info: colors.sky,
        success: colors.teal,
        warning: colors.amber,
        danger: colors.rose,
      },
    },
  },
  // ...rest of config
}

Tailwind CSS provides 22 pre-built colors, each one having 10 different shades, from the lightest one to the darkest one. Let's talk about some of our choices. First, even if the system is pretty complete, we felt it needed an additional really darker shade to enhance dark mode renders. Therefore we decided to add an additional shade to the gray based Tailwind colors.

WARNING

Consider the slate, gray, zinc, neutral and stone colors like base grey-ish backgrounds and tints for your design system and UI elements. Avoid at all costs mixing them as it will affect the consistency of your color system. Instead use one of those consistently throughout your design.

Following the gold rule in the above warning, we've decided to abstract the gray color we want to use for the template inside the muted variable.

javascript
  muted: colors.slate,

That simply is a shortcut to use our slate color through the template without taking the risk of mixing with another tint of gray. Therefore, instead of doing:

html
<span class="text-slate-600"></span>

We now simply write:

html
<span class="text-muted-600"></span>

Yeah, we pretty much know what you're going to say: "Hold on, this is pretty and nice, but how is it going to help me with my development? What benefit is this bringing?".

  • First, you don't take the risk of mixing inconsistent gray shades, having gray in some parts, slate in others, and maybe stone somewhere else. The main gray shade is abstracted in the muted color variable and prevents any potential errors, like when multiple people work on the same project.
  • Second, if you want to change the overall look of your app, you can do it anytime. You just need to switch the color linked to the muted variable, like this:
javascript
  // If you want a slate dominant in your layout
  muted: colors.slate,

  // If you want a gray dominant in your layout
  muted: colors.gray,

  // If you want a zinc dominant in your layout
  muted: colors.zinc,

  // If you want a neutral dominant in your layout
  muted: colors.neutral,

  // If you want a stone dominant in your layout
  muted: colors.stone,

we use the same mechanism to abstract other colors that you might need in a web project, like a primary color or other colors like success and danger that symbolize some application states. You can configure those colors as shortcuts as well:

javascript
  //The template primary color
  primary: colors.indigo,

  //The blue-ish info state
  info: colors.sky,

  //The green-ish success state
  success: colors.teal,

  //The orange-ish warning state
  warning: colors.amber,

  //The red-ish danger state
  danger: colors.rose,

Therefore, instead of using the indigo color, we abstract it to primary:

html
<!--You shouldn't do that-->
<span class="text-indigo-600"></span>

<!--This is the right way-->
<span class="text-primary-600"></span>

You should now have a good understanding of how colors work in Xpulse. Take a look at how we use them inside the template to get a more precise idea of the possibilities.

Fonts

Tailwind CSS provides a very simple way to abstract your fonts, like we did with colors. If you don't know anything about Tailwind font families, we recommend that you take a look at the Tailwind docs to learn more about how to configure and use them. In Xpulse, we use the theme section of the configuration file to configure our own fonts. It also means, that you can do the same thing to change all the template fonts to match your branding. Here is the color configuration of Xpulse:

javascript
module.exports = {
  // ...previous config elements
  theme: {
    extend: {
      fontFamily: {
        sans: ['Inter', 'sans-serif'],
      },
    },
  },
  // ...rest of config
}

We use the Inter font as our main font. You then call this font in your templates by using the font-sans class expression. The font source is configured inside the src/utils/fonts.ts file. We load the Inter font from the next/font/google package:

javascript
import { Inter } from 'next/font/google'

export const inter = Inter({
  variable: '--font-inter',
  weight: ['200', '300', '400', '500', '700', '900'],
  subsets: ['latin'],
})

WARNING

Only defining your font in the tailwind.config.js file is not enough. You also need to load the font source in your application. We do it in the src/utils/fonts.ts file, but you can do it anywhere you want, as long as it is loaded before the tailwind.config.js file.

You could also follow the same pattern if you want to customize the native font-serif and font-mono stacks that Tailwind provides by default. You could add something like this in the configuration above:

javascript
module.exports = {
  // ...previous config elements
  theme: {
    extend: {
      fontFamily: {
        serif: ['Some Font Name', 'serif'],
        mono: ['Some Font Name', 'monospace'],
      },
    },
  },
  // ...rest of config
}

Keyframes

Keyframes are animation definitions written in CSS. While Tailwind provides many of these out-of-the-box, we still needed to add specific ones for our template, using that theme configuration section of the tailwind.config.js file:

javascript
module.exports = {
  // ...previous config elements
  theme: {
    extend: {
      keyframes: {
        indeterminate: {
          '0%': { 'margin-left': '-10%' },
          '100%': { 'margin-left': '100%' },
        },
        'circle-chart-fill': {
          to: {
            'stroke-dasharray': '0 100',
          },
        },
        wave: {
          '0%': {
            transform: 'scale(1)',
            opacity: '1',
          },

          '25%': {
            transform: 'scale(1)',
            opacity: '1',
          },

          '100%': {
            transform: 'scale(4.5)',
            opacity: '0',
          },
        },
        fadeInUp: {
          from: {
            transform: 'translate3d(0, 20px, 0)',
          },

          to: {
            transform: 'translate3d(0, 0, 0)',
            opacity: 1,
          },
        },
        fadeInLeft: {
          from: {
            transform: 'translate3d(20px, 0, 0)',
            opacity: '0',
          },
          to: {
            transform: 'translate3d(0, 0, 0)',
            opacity: '1',
          },
        },
        spinAround: {
          from: {
            transform: 'rotate(0deg)',
          },

          to: {
            transform: 'rotate(359deg)',
          },
        },
      },
    },
  },
  // ...rest of config
}

We extend the Tailwind default keyframes by adding two of our own animations, that we will be using in some components.

animations

Like we saw in the previous section, we've added some new keyframes. We're now going to use them in new animation we are also going to add to Tailwind CSS.

javascript
module.exports = {
  // ...previous config elements
  theme: {
    extend: {
      animation: {
        'spin-slow': 'spin 3s linear infinite',
        'spin-fast': 'spin 0.65s linear infinite',
        indeterminate: 'indeterminate 1s cubic-bezier(0.4, 0, 0.2, 1) infinite',
      },
    },
  },
  // ...rest of config
}

We are declaring the indeterminate and spin-* animation that make use of the keyframes we've defined earlier. We also add the animation-timing-function and duration properties, like in traditional CSS. Once our animations are added, we can then simply use them in our HTML template by calling the animate-indeterminate or the animate-spin-* classes.

Adding plugin utilities

In Xpulse, we use the plugin object to add flat utilities directly in the configuration file:

javascript
module.exports = {
  // ...previous config elements
  theme: {
    extend: {
      plugins: [
        plugin(({ addComponents, addVariant }) => {
          //target progress container
          addVariant('progress-container', '&::-webkit-progress-bar')

          //target progress bar/inner
          addVariant('progress-bar', ['&::-webkit-progress-value', '&::-moz-progress-bar', '&::-ms-fill'])
        }),
        function ({ addBase, theme }) {
          function extractColorVars(colorObj, colorGroup = '') {
            return Object.keys(colorObj).reduce((vars, colorKey) => {
              const value = colorObj[colorKey]

              const newVars = typeof value === 'string' ? { [`--color${colorGroup}-${colorKey}`]: value } : extractColorVars(value, `-${colorKey}`)

              return { ...vars, ...newVars }
            }, {})
          }

          addBase({
            ':root': extractColorVars(theme('colors')),
          })
        },
      ],
    },
  },
  // ...rest of config
}

The classes added above will are available everywhere in the project and are also compatible with the Tailwind responsive-variants that rely on the breakpoints you've defined earlier.

Adding plugin components

we can also use the plugin object to add flat CSS components directly in the configuration file:

javascript
module.exports = {
  // ...previous config elements
  theme: {
    extend: {
      plugins: [
        plugin(function ({ addComponents }) {
          addComponents({
            '.slimscroll::-webkit-scrollbar': {
              width: '6px',
            },
            '.slimscroll::-webkit-scrollbar-thumb': {
              'border-radius': '.75rem',
              background: 'rgba(0, 0, 0, 0.1)',
            },
            '.slimscroll-opaque::-webkit-scrollbar-thumb': {
              background: 'rgba(0, 0, 0, 0) !important',
            },
            '.mask': {
              'mask-size': 'contain',
              'mask-repeat': 'no-repeat',
              'mask-position': 'center',
            },
            '.mask-hex': {
              'mask-image':
                "url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE4MiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNjQuNzg2IDE4MS40Yy05LjE5NiAwLTIwLjA2My02LjY4Ny0yNS4wNzktMTQuMjFMMy43NjIgMTA1LjMzYy01LjAxNi04LjM2LTUuMDE2LTIwLjkgMC0yOS4yNTlsMzUuOTQ1LTYxLjg2QzQ0LjcyMyA1Ljg1MSA1NS41OSAwIDY0Ljc4NiAwaDcxLjA1NWM5LjE5NiAwIDIwLjA2MyA2LjY4OCAyNS4wNzkgMTQuMjExbDM1Ljk0NSA2MS44NmM0LjE4IDguMzYgNC4xOCAyMC44OTkgMCAyOS4yNThsLTM1Ljk0NSA2MS44NmMtNC4xOCA4LjM2LTE1Ljg4MyAxNC4yMTEtMjUuMDc5IDE0LjIxMUg2NC43ODZ6Ii8+PC9zdmc+')",
            },
            '.mask-hexed': {
              'mask-image':
                "url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgyIiBoZWlnaHQ9IjIwMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNLjMgNjUuNDg2YzAtOS4xOTYgNi42ODctMjAuMDYzIDE0LjIxMS0yNS4wNzhsNjEuODYtMzUuOTQ2YzguMzYtNS4wMTYgMjAuODk5LTUuMDE2IDI5LjI1OCAwbDYxLjg2IDM1Ljk0NmM4LjM2IDUuMDE1IDE0LjIxMSAxNS44ODIgMTQuMjExIDI1LjA3OHY3MS4wNTVjMCA5LjE5Ni02LjY4NyAyMC4wNjMtMTQuMjExIDI1LjA3OWwtNjEuODYgMzUuOTQ1Yy04LjM2IDQuMTgtMjAuODk5IDQuMTgtMjkuMjU4IDBsLTYxLjg2LTM1Ljk0NUM2LjE1MSAxNTcuNDQuMyAxNDUuNzM3LjMgMTM2LjU0VjY1LjQ4NnoiLz48L3N2Zz4=')",
            },
            '.mask-deca': {
              'mask-image':
                "url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTkyIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNOTYgMGw1OC43NzkgMTkuMDk4IDM2LjMyNyA1MHY2MS44MDRsLTM2LjMyNyA1MEw5NiAyMDBsLTU4Ljc3OS0xOS4wOTgtMzYuMzI3LTUwVjY5LjA5OGwzNi4zMjctNTB6IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=')",
            },
            '.mask-blob': {
              'mask-image':
                "url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTAwIDBDMjAgMCAwIDIwIDAgMTAwczIwIDEwMCAxMDAgMTAwIDEwMC0yMCAxMDAtMTAwUzE4MCAwIDEwMCAweiIvPjwvc3ZnPg==')",
            },
            '.mask-diamond': {
              'mask-image':
                "url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTAwIDBsMTAwIDEwMC0xMDAgMTAwTDAgMTAweiIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9zdmc+')",
            },
          })
        }),
      ],
    },
  },
  // ...rest of config
}

The classes added above will are available everywhere in the project. They work like traditional CSS. Also, keep in mind that unlike the plugin utility classes we've added earlier, these component utilities are do not generate responsive-variants.

Adding Custom Styles:

You have the flexibility to incorporate custom styles in different ways within your project.

  1. Adding global styles

You can introduce custom styles by defining CSS classes in your src/styles/global.css file:

css
@tailwind base;
@tailwind components;
@tailwind utilities;

.btn-fav {
  padding: 10px;
}

By declaring class btn-fav in global.css , you make it accessible throughout your project, and it can be utilized anywhere.

  1. Adding Inline Styles to React Components
jsx
import Button from '@/components/elements/buttons/Button'

export default function Login() {
  return (
    <div>
      <Button color="primary" variant="raised" className="!h-12 w-full mb-5 mt-2 shadow">
        Login
      </Button>

      {/* more component logic */}
    </div>
  )
}

This approach allows you to conveniently pass your custom classes via the className prop, and they will be applied to components that accept class assignments.

  1. Advanced Styling Options: Tailwind Components, Utilities, and More

For more advanced customization, Tailwind CSS offers the ability to create custom styles through its components , utilities, and other methods. These options allow you to extend Tailwind CSS to suit your project's specific styling needs.

Component Customization

Some components offer the ability to customize their styling using props, such as the <Button/> component, which accepts both variant and color props, in addition to its default button attributes.

The styles for the buttons, referred to as buttonStyles, are generated using Class Variance Authority to prevent conflicts between classes. These styles can also be applied to elements like <Link> or <a> to give them the appearance of a button.

Furthermore, you can override classes in the base styles of components by prefixing them with an exclamation mark !, making the class important.

jsx
import Button from '@/components/elements/buttons/Button'
import { buttonStyles } from '@/components/elements/colors/btn-colors'

const Invoice = () => {
  return (
    <div>
      {/* Button with primary color and raised appearance */}
      <Button color="primary" variant="solid" className="mx-1 !min-w-[auto] flex-grow-[2]">
        Edit
      </Button>

      {/* classes will be appended to the Default styling */}
      <Button className="mx-1 !min-w-[auto] flex-grow-[2]">Share</Button>

      {/* Default styling for the button */}
      <Link
        href="/content-invoice"
        className={buttonStyles({
          className: 'h-9 min-w-[56px] !rounded-r-none px-4 py-2 text-center',
        })}
      >
        View
      </Link>
    </div>
  )
}

TIP

If you are using Visual Studio Code (VSCode) you can hit Ctrl + Space to see all the available props of a component

Conditional Styling

Implementing conditional styling based on dynamic data or user interactions can be achieved as below.

  1. Utilizing a string literal for conditional styling based on state or props:
jsx
const IconButton: FC<{
  shape?: 'square' | 'round',
}> = ({ shape = 'square', className: classes }) => {
  return <button className={` ${shape === 'square' ? 'rounded-xl ' : 'rounded-full'} h-[44px] w-[44px] min-w-[44px] ${classes}`}></button>
}

In this approach, the class rounded-xl is applied when shape equals "square," otherwise, rounded-full is applied.

  1. Leveraging Tailwind CSS variants or modifiers
jsx
import Button from '@/components/elements/buttons/Button'

const Component = () => <Button className="dark:hover:border-muted-800">Edit</Button>

the class dark:hover:border-muted-800 is applied when the element is hovered in dark mode.

You should know understand how to customize the template to match your branding. Let's now take a look at how to deploy the template.

All Rights Reserved