Toggle as an Edge Case of Using Tailwind CSS

Toggle as an Edge Case of Using Tailwind CSS

23 Jan 2023

23 Jan 2023

Klaudia Wereniewicz

Klaudia Wereniewicz

Toggle as an Edge Case of Using Tailwind CSS - cover graphic
Toggle as an Edge Case of Using Tailwind CSS - cover graphic
Toggle as an Edge Case of Using Tailwind CSS - cover graphic

Want to optimize your CSS for speed and simplicity in frontend development? Discover how we leveraged the component-centric philosophy of Tailwind CSS to boost our development pace and cut down on code redundancy.

Introduction

Modern frontend applications present a constant challenge in the writing of reliable and simple to maintain CSS code. In one of our projects, we used to rely on SCSS modules as a tool to control element styles in a more granular way and to resolve problems related to unexpected class conflicts. However, we found that even with SCSS modules, we were still facing growing code complexity and duplication of code over time.

One of the main issues we faced with SCSS modules was code duplication. When the time comes to update a piece of code that appears in multiple places, it can be easy to forget to change it everywhere, breaking the principle of DRY (Don't Repeat Yourself) programming.

Given these challenges, we decided to explore the atomic CSS approach. This approach is more compatible with our design system and aligns with the principles of atomic design, which we have written about before here. To implement this approach, we chose to use Tailwind CSS as our tool.


Introducing Tailwind CSS

Tailwind CSS is a highly popular CSS framework that utilises atomic design or component-based design principles. It allows developers to build responsive layouts and increases the speed of development by providing built-in classes that do not require starting designs from scratch.

An example of a React component using Tailwind CSS might look like this:

import clsx from 'clsx';

import { TagColor } from '@shared/colors';

interface Props {
  label: string;
  color: TagColor;
}

const colors: Record<TagColor, string> = {
  [TagColor.RED]: 'bg-danger text-white',
  [TagColor.YELLOW]: 'bg-yellow text-black',
  [TagColor.GREEN]: 'bg-green text-white',
};

export const Tag = ({ color, label }: Props) => {
  return (
    <li className={clsx(colors[color], 'w-max list-none rounded py-1 px-2')}>
      <span>{label}</span>
    </li>
  );
};


One of the main advantages of using Tailwind CSS is that it provides only one source of truth when it comes to configuration. Also, you don’t need to think of names of classes as it has got own set of utility classes. Alongside, it gives options for customisation of colours, breakpoints or complex styles like gradients.

On the other hand, there are also some downsides to using Tailwind CSS, such as a steeper learning curve, as it has got a lot of pre-defined classes to remember, even if you are familiar with CSS. And it can lead to some cases where the className in some JSX tags can become very long. i.e. sticky bottom-0 flex h-16 w-full items-center justify-between border-t border-t-grayscale-500 bg-white px-4.

Despite these downsides, overall Tailwind CSS offers a powerful and highly customisable solution for working with CSS in modern frontend applications.


Development

As a team, we decided to use Tailwind CSS in the project by utilising utility classes inline everywhere possible, decreasing the count of standalone SCSS files in our project.

One of the things we encountered during development was that if you use any methodology, e.g. BEM (Block Element Modifier), then writing styles using utility classes directly in HTML may not suit you. For this issue, one way to solve it is by combining classes to existing ones by using the @apply directive, which copies original styles of used utility classes into code that it was called. However, Tailwind CSS recommend not to use @apply directive to make code more clearer.

As an example of the usage of Tailwind CSS, we will look at how we styled our switch component. In the original code, we used Tailwind's pre-defined classes directly in the component, which resulted in a very long className.

<input
	type="checkbox"
	className={clsx(
		'box-content grid h-5 w-10 cursor-pointer appearance-none items-center rounded-3xl border-0 before:h-4 before:w-4 checked:before:translate-x-[22px]',
		'bg-grayscale-500 checked:bg-primary-500 hover:bg-grayscale-600 checked:hover:bg-primary-600',
		"before:pointer-events-auto before:translate-x-0.5 before:rounded-full before:bg-white before:shadow before:content-['']",
		'before:transition before:duration-200 before:ease-in-out',
		'disabled:pointer-events-none',
	)}
	// rest of props
/>

As an alternative, we could have used the above classes in a separated SCSS file with @apply directive.

input[type='checkbox'] {
    @apply box-content grid cursor-pointer items-center appearance-none rounded-3xl border-0 bg-secondary-500 checked:bg-primary-500 hover:bg-secondary-600 checked:hover:bg-primary-600 before:pointer-events-auto before:translate-x-0.5 before:rounded-full before:bg-white before:shadow before:content-[''] before:transition before:duration-200 before:ease-in-out;
}

Another alternative without using modifiers like before:, after:, or hover:

input[type='checkbox'] {
  @apply box-content grid h-5 w-10 cursor-pointer appearance-none items-center rounded-3xl border-0;

  &:checked {
    @apply bg-primary-500;

    &:hover {
      @apply bg-primary-600;
    }

    &:before {
      @apply translate-x-[22px];
    }
  }

  &:hover {
    @apply bg-secondary-600;
  }

  &:before {
    @apply w-4 h-4 pointer-events-auto translate-x-0.5 rounded-full bg-white shadow content-[''] transition duration-200 ease-in-out;

  }
}

In this way, you can use the benefits of Tailwind CSS, while still adhering to your preferred methodology and keeping your code organised and readable. By extracting classes, you can make your code more maintainable and easier to understand in the future. It also allows to keep track of changes and updates in a centralised location. This makes it easier to manage complex styling while keeping the design system consistent across the application.


Conclusion

In conclusion, using Tailwind CSS everywhere in the project has been a valuable experience for our team. However, we found that it was an overkill for simple components like a switch. While implementing Tailwind CSS, we realised that using it everywhere where it's possible might not always be the best idea. We should not be so strict with that rule.

We also found that it can be hard to avoid using raw CSS in separate files if we want to have readable code in some cases. Using Tailwind classes can be too complicated for very simple components, which can result in very long className in their JSX tags.

Overall, Tailwind CSS is a great solution for developers who are familiar with CSS and want to speed up the development of their projects. However, sometimes ejecting from the default tool might bring value and can be necessary to create a readable and maintainable solution. This doesn't only apply to Tailwind, but it can apply to all alternatives.

Want to optimize your CSS for speed and simplicity in frontend development? Discover how we leveraged the component-centric philosophy of Tailwind CSS to boost our development pace and cut down on code redundancy.

Introduction

Modern frontend applications present a constant challenge in the writing of reliable and simple to maintain CSS code. In one of our projects, we used to rely on SCSS modules as a tool to control element styles in a more granular way and to resolve problems related to unexpected class conflicts. However, we found that even with SCSS modules, we were still facing growing code complexity and duplication of code over time.

One of the main issues we faced with SCSS modules was code duplication. When the time comes to update a piece of code that appears in multiple places, it can be easy to forget to change it everywhere, breaking the principle of DRY (Don't Repeat Yourself) programming.

Given these challenges, we decided to explore the atomic CSS approach. This approach is more compatible with our design system and aligns with the principles of atomic design, which we have written about before here. To implement this approach, we chose to use Tailwind CSS as our tool.


Introducing Tailwind CSS

Tailwind CSS is a highly popular CSS framework that utilises atomic design or component-based design principles. It allows developers to build responsive layouts and increases the speed of development by providing built-in classes that do not require starting designs from scratch.

An example of a React component using Tailwind CSS might look like this:

import clsx from 'clsx';

import { TagColor } from '@shared/colors';

interface Props {
  label: string;
  color: TagColor;
}

const colors: Record<TagColor, string> = {
  [TagColor.RED]: 'bg-danger text-white',
  [TagColor.YELLOW]: 'bg-yellow text-black',
  [TagColor.GREEN]: 'bg-green text-white',
};

export const Tag = ({ color, label }: Props) => {
  return (
    <li className={clsx(colors[color], 'w-max list-none rounded py-1 px-2')}>
      <span>{label}</span>
    </li>
  );
};


One of the main advantages of using Tailwind CSS is that it provides only one source of truth when it comes to configuration. Also, you don’t need to think of names of classes as it has got own set of utility classes. Alongside, it gives options for customisation of colours, breakpoints or complex styles like gradients.

On the other hand, there are also some downsides to using Tailwind CSS, such as a steeper learning curve, as it has got a lot of pre-defined classes to remember, even if you are familiar with CSS. And it can lead to some cases where the className in some JSX tags can become very long. i.e. sticky bottom-0 flex h-16 w-full items-center justify-between border-t border-t-grayscale-500 bg-white px-4.

Despite these downsides, overall Tailwind CSS offers a powerful and highly customisable solution for working with CSS in modern frontend applications.


Development

As a team, we decided to use Tailwind CSS in the project by utilising utility classes inline everywhere possible, decreasing the count of standalone SCSS files in our project.

One of the things we encountered during development was that if you use any methodology, e.g. BEM (Block Element Modifier), then writing styles using utility classes directly in HTML may not suit you. For this issue, one way to solve it is by combining classes to existing ones by using the @apply directive, which copies original styles of used utility classes into code that it was called. However, Tailwind CSS recommend not to use @apply directive to make code more clearer.

As an example of the usage of Tailwind CSS, we will look at how we styled our switch component. In the original code, we used Tailwind's pre-defined classes directly in the component, which resulted in a very long className.

<input
	type="checkbox"
	className={clsx(
		'box-content grid h-5 w-10 cursor-pointer appearance-none items-center rounded-3xl border-0 before:h-4 before:w-4 checked:before:translate-x-[22px]',
		'bg-grayscale-500 checked:bg-primary-500 hover:bg-grayscale-600 checked:hover:bg-primary-600',
		"before:pointer-events-auto before:translate-x-0.5 before:rounded-full before:bg-white before:shadow before:content-['']",
		'before:transition before:duration-200 before:ease-in-out',
		'disabled:pointer-events-none',
	)}
	// rest of props
/>

As an alternative, we could have used the above classes in a separated SCSS file with @apply directive.

input[type='checkbox'] {
    @apply box-content grid cursor-pointer items-center appearance-none rounded-3xl border-0 bg-secondary-500 checked:bg-primary-500 hover:bg-secondary-600 checked:hover:bg-primary-600 before:pointer-events-auto before:translate-x-0.5 before:rounded-full before:bg-white before:shadow before:content-[''] before:transition before:duration-200 before:ease-in-out;
}

Another alternative without using modifiers like before:, after:, or hover:

input[type='checkbox'] {
  @apply box-content grid h-5 w-10 cursor-pointer appearance-none items-center rounded-3xl border-0;

  &:checked {
    @apply bg-primary-500;

    &:hover {
      @apply bg-primary-600;
    }

    &:before {
      @apply translate-x-[22px];
    }
  }

  &:hover {
    @apply bg-secondary-600;
  }

  &:before {
    @apply w-4 h-4 pointer-events-auto translate-x-0.5 rounded-full bg-white shadow content-[''] transition duration-200 ease-in-out;

  }
}

In this way, you can use the benefits of Tailwind CSS, while still adhering to your preferred methodology and keeping your code organised and readable. By extracting classes, you can make your code more maintainable and easier to understand in the future. It also allows to keep track of changes and updates in a centralised location. This makes it easier to manage complex styling while keeping the design system consistent across the application.


Conclusion

In conclusion, using Tailwind CSS everywhere in the project has been a valuable experience for our team. However, we found that it was an overkill for simple components like a switch. While implementing Tailwind CSS, we realised that using it everywhere where it's possible might not always be the best idea. We should not be so strict with that rule.

We also found that it can be hard to avoid using raw CSS in separate files if we want to have readable code in some cases. Using Tailwind classes can be too complicated for very simple components, which can result in very long className in their JSX tags.

Overall, Tailwind CSS is a great solution for developers who are familiar with CSS and want to speed up the development of their projects. However, sometimes ejecting from the default tool might bring value and can be necessary to create a readable and maintainable solution. This doesn't only apply to Tailwind, but it can apply to all alternatives.

Start a project

Are you a changemaker? Elevate your vision with a partnership dedicated to amplifying your impact!

💌 Join our newsletter

Receive insightful, innovator-focused content from global product experts — directly in your mail box, always free

Address & company info


Chmielna 73B / 14,
00-801 Warsaw, PL

VAT-EU (NIP): PL7831824606
REGON: 387099056
KRS: 0000861621

💌 Join our newsletter

Receive insightful, innovator-focused content from global product experts — directly in your mail box, always free

Address & company info


Chmielna 73B / 14,
00-801 Warsaw, PL

VAT-EU (NIP): PL7831824606
REGON: 387099056
KRS: 0000861621

💌 Join our newsletter

Receive insightful, innovator-focused content from global product experts — directly in your mail box, always free

Address & company info


Chmielna 73B / 14,
00-801 Warsaw, PL

VAT-EU (NIP): PL7831824606
REGON: 387099056
KRS: 0000861621