DEV Community

Cover image for Designing Multi-Tenant UI with Tailwind CSS
@Jonath-z
@Jonath-z

Posted on

Designing Multi-Tenant UI with Tailwind CSS

Tailwind CSS is one of the most popular and widely used CSS libraries. It leverages utility classes to build responsive designs without having to write extensive CSS. It does not use pre-styled components like Bootstrap or Material UI.

In this article, we are going to focus on how to build custom variants for a multi-tenant frontend application.

This article is for those with a basic understanding of CSS and Tailwind CSS. You don't need to be a frontend expert to follow along.

Concept of Variants in Tailwind CSS

Tailwind CSS variants are sets of predefined classes that allow developers to apply certain styles conditionally. By using variants, developers can create different styles for a component or element according to different states. For instance, when a button is disabled, the disabled: variant can be used followed by the utility class to apply a specific style.

In Tailwind, there are many predefined variants that we can classify into five categories:

  1. Responsive variants: These variants are used to apply certain styles according to screen dimensions. In this category, we have sm:, md:, max-lg:, etc.

  2. Pseudo-class variants: These variants are used to apply styles based on specific state conditions. In this category, we have hover:, enabled:, checked:, etc.

  3. Group variant: The group variant is used to style elements according to the state of the group to which the element belongs. It is written as group:.

  4. Peer variant: The peer variant is used to style an element based on the state of a peer element. In this case, the component does not have to belong to a group, it is styled according to the state of a parallel component. It is written as peer:.

  5. Custom variants: Custom variants are implemented with the addVariant API.

Custom Variants

A custom variant can be added to Tailwind CSS as a plugin using the addVariant API. This API allows developers to extend Tailwind CSS by implementing customized variants that can be used like the built-in ones. One well-known custom variant, now adopted as a default in Tailwind CSS, is dark:, which is used to apply styles when the application is in dark mode.

1. Creating a Simple Custom Variant

The code below shows how a custom variant can be implemented as a plugin in tailwind.config.js:

const plugin = require('tailwindcss/plugin');

module.exports = {
  plugins: [
    plugin(function({ addVariant }) {
      addVariant('custom', '&:optional');
    })
  ]
};
Enter fullscreen mode Exit fullscreen mode

We use the plugin function from tailwindcss/plugin, which gives us access to the addVariant method. The addVariant method takes two arguments: the first is the name of the variant, and the second is its definition, which can be a string or a function for adding more support with other predefined variants like group: and peer:.

2. Custom Variant with Modifiers

For this step, we'll understand how modifiers can create a scope for all descendant class utilities. The dark: modifier for dark mode is implemented similarly.

Before diving deeper, let's understand how the dark: modifier works in Tailwind CSS.

For applying dark mode styles to our components or elements, we can use these approaches:

<div className="text-white dark:text-black"></div>
Enter fullscreen mode Exit fullscreen mode

In this first approach, we explicitly add the modifier to the element to change its styles in dark mode, which is straightforward. However, an application is not just a single div; it often has hundreds of components with different colors in dark mode, which leads us to the second approach.

<html className="dark">
   <div className="text-foreground"></div>
</html>
Enter fullscreen mode Exit fullscreen mode

In this second approach, the variant is applied only to the html tag, considered the top-level tag of our application or page. We use a custom color name, which changes the color value according to the variant placed on the top-level html tag.

To achieve this pattern, we don't only need a custom variant but also a modifier to modify all the class utilities scoped under the variant applied to the top-level tag.

Here's how to add a plugin with a modifier:

plugin(function ({ addVariant, e }) {
  addVariant("dark", ({ modifySelectors, separator }) => {
    modifySelectors(({ className }) => {
      return `.dark .${e(`dark${separator}${className}`)}`;
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

In this code, the callback function from the plugin function also destructures e, a function to escape strings. The callback returns a string that defines how the variant should modify the selector:

  • .dark: Targets any element with the class "dark."
  • A space: Creates a descendant combinator, which is used to select elements that are descendants of another element.
  • .: Starts a new class selector.
  • e(dark${separator}${className}): Generates the variant class name.

The generated CSS will look like this:

.dark .dark\:text-foreground {
  /* styles here */
}
Enter fullscreen mode Exit fullscreen mode

Now that we understand how to create simple custom variants and custom variants with modifiers, we will use these principles for the multi-tenant UI design problem.

Multi-tenant UI Design with Tailwind CSS

For this article, we'll focus on the frontend of a multi-tenant architecture. A multi-tenant frontend application serves multiple customers or groups (tenants) with a single application without requiring separate instances or code changes for each tenant.

1. Problem and Requirements

Let's consider two tenants, TenantA and TenantB, both with different branding and types of data, but they share the same frontend application. The branding color of TenantA is green, while TenantB's is yellow. Both tenants are restaurants and use the same frontend application but want it to reflect their branding.

2. Solution

To meet the requirements of TenantA and TenantB, we will follow a few steps:

1. Defining Colors Globally

One key to a successful multi-tenant frontend UI design is a good base structure for defining color names. Since both tenants will share the same components, with only different styling, the color names should be the same, with different values. In the CSS entry point, which could be main.css, global.css, or index.css depending on the structure, we define a color name called branding-color at the root level as a CSS variable.

:root {
  .tenantA {
    --branding-color: #008000;
  }

  .tenantB {
    --branding-color: #FFFF00;
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Setting Colors in Tailwind CSS Config

After defining the branding colors globally, Tailwind CSS is not yet aware of our colors, so we need to set them up in the tailwind.config.js file under the extended colors object.

module.exports = {
  extend: {
    colors: {
      "branding-color": "var(--branding-color)"
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

3. Creating Tenant Plugins

The colors are configured in Tailwind, but they cannot be used for the two tenants without creating their respective plugins with modifiers.

const plugin = require('tailwindcss/plugin');

module.exports = {
  extend: {
    colors: {
      "branding-color": "var(--branding-color)"
    }
  },
  plugins: [
    plugin(function({ addVariant, e }) {
      addVariant("tenantA", ({ modifySelectors, separator }) => {
        modifySelectors(({ className }) => {
          return `.tenantA .${e(`tenantA${separator}${className}`)}`;
        });
      });
      addVariant("tenantB", ({ modifySelectors, separator }) => {
        modifySelectors(({ className }) => {
          return `.tenantB .${e(`tenantB${separator}${className}`)}`;
        });
      });
    })
  ]
};
Enter fullscreen mode Exit fullscreen mode

Now, we have configured the tenant plugins, and we can easily use them to apply different colors for different tenants:

<html className="tenantA">
   <div className="text-branding-color"></div>
</html>
Enter fullscreen mode Exit fullscreen mode
<html className="tenantB">
   <div className="text-branding-color"></div>
</html>
Enter fullscreen mode Exit fullscreen mode

The only change is in the class on the top-level tag, which is enough for the entire application to have a different look.

Conclusion

As multi-tenant frontend applications are usually large and complex, Tailwind CSS variants can be used to have smooth transitions between colors and control the visibility of different components, similar to normal default variants.

Thanks for reading.

Learn More:

Top comments (1)

Collapse
 
ibrahimbagalwa profile image
Ibrahim Bagalwa

Nice one šŸ‘