Large Tailwind Components — What to do About All Those Classes
Summary
TLDRThe video script discusses the challenges of managing complex CSS class names in Tailwind, particularly for components with multiple variants like buttons. It introduces Class Variants Authority (CVA) as a solution, demonstrating how it simplifies the creation and maintenance of consistent, structured styles for components within a design system. The script compares traditional Tailwind CSS with CVA, showing how CVA can improve readability and maintainability, especially beneficial for large-scale design systems with numerous component variants.
Takeaways
- 😕 Tailwind can be messy when defining class names for components with many variants, like a button with different styles and sizes.
- 🛠️ The speaker used 'stitches' for styling in CSS and JS, highlighting its structured approach to defining component styles based on props.
- 🔍 'Class Variants Authority' (CVA) is a library that helps manage Tailwind class names for complex components, similar to 'stitches'.
- 🎨 The video aims to build a button component from Shopify's design system, comparing the process with and without using CVA.
- 📝 The speaker discusses the importance of preventing the creation of invalid component variants in a design system.
- 🧩 The process involves defining props and variants for the button, including size, modifier, and boolean properties like 'disabled' and 'loading'.
- 📉 The speaker identifies issues with maintaining and readability when using plain Tailwind for complex components.
- 🔄 The video demonstrates how to convert a Tailwind-based button to use CVA, streamlining the class name generation process.
- 🛑 The script points out the need to avoid duplicate CSS properties in Tailwind to prevent specificity issues and potential bugs.
- 🔑 CVA's structured approach helps in maintaining a consistent style application across variants and reduces the risk of typos.
- 🚀 The video concludes that CVA is beneficial for large components within design systems, but should be used judiciously to avoid overcomplication.
Q & A
What is the main issue discussed in the script regarding Tailwind CSS?
-The script discusses the issue of creating a big mess when defining class names for components with many variants in Tailwind CSS, especially when those components have various properties that change the class names.
What is a potential solution to the class name mess in Tailwind CSS mentioned in the script?
-The script suggests using a library called class-variant-authority (CVA) as a potential solution to manage class names more effectively in Tailwind CSS.
What is Stitches and how does it relate to the problem discussed?
-Stitches is a library mentioned in the script that was used for styling components in CSS and JS. It allows defining an object called 'variants' to manage different properties of a component, which is similar to what CVA does for Tailwind CSS.
What is a compound variant in the context of the script?
-A compound variant in the script refers to the combination of different variants of a component, such as a primary button that is also disabled, which can affect the styling in a way that needs to be managed.
What is the purpose of using CVA according to the script?
-The purpose of using CVA is to improve the maintainability and readability of the code when dealing with complex components that have many different variants and to avoid the mess of manually defining numerous class names.
What is the 'variants' object in CVA and how is it used?
-The 'variants' object in CVA is used to define different styles for a component based on its properties. It is an object where each property can nest different properties, and each of these will map to a property of the component, affecting the rendered CSS.
How does the script suggest improving the workflow when building a button component with many variants?
-The script suggests using CVA to generate class names based on the button's properties and variants, which can streamline the process and reduce the potential for errors and duplication in the code.
What is the downside mentioned in the script about creating too many variants?
-The downside mentioned is the possibility of creating variants that are not intended or do not make sense, such as a 'primary destructive' button, which could lead to inconsistencies in the design system.
What is the importance of specificity and order in CSS when using Tailwind CSS, as discussed in the script?
-The script highlights that in Tailwind CSS, if multiple classes with the same specificity are defined, the one that appears last in the CSS file will take precedence. This can lead to unexpected results if duplicate properties are defined.
What is the recommendation for using CVA in the script?
-The script recommends using CVA for complex components or large design systems where maintaining a structured and predictable codebase is crucial. It should not be used as the default for every component to avoid overcomplicating simple implementations.
Outlines
😖 Tailwind's Class Name Complexity
The speaker expresses frustration with Tailwind CSS's flexibility, which can lead to messy class names when defining variants for components like buttons. They mention the challenge of handling different states and sizes, which can result in a disorganized codebase. The speaker contrasts this with their previous experience using the Stitches library, which provided a structured approach to defining component styles based on props.
🛠 Introduction to Class Variants Authority (CVA)
The speaker introduces Class Variants Authority (CVA), a library that aims to bring structure to Tailwind CSS by allowing developers to define variants and modifiers in a more organized manner. They compare CVA's approach to Stitches and demonstrate how it can be used to create a button component with various styles and states, making the process more maintainable and less prone to errors.
🔍 Deep Dive into Tailwind Class Name Generation
The speaker provides an in-depth look at generating Tailwind class names for a complex button component, highlighting the difficulties in managing different sizes, states, and modifiers. They discuss the specificity of CSS and the importance of avoiding duplicate properties to prevent unexpected behavior in production. The speaker also points out a bug in their code related to padding properties and suggests a fix.
📝 Implementing CVA for Better Maintainability
The speaker begins the process of converting their button component to use CVA, emphasizing the improved structure and readability it offers. They walk through the steps of defining shared class names, variants, and modifiers within CVA's configuration. The speaker also discusses the benefits of using CVA for creating design systems and managing complex components.
🔄 Refactoring with CVA for Enhanced Structure
The speaker continues the refactoring process, moving various properties and modifiers into the CVA configuration. They address the challenge of defining compound variants and suggest improvements to CVA that would reduce the need for duplicate code. The speaker also discusses the importance of consistent code patterns for maintainability within large projects.
🎉 Conclusion on Using CVA in Design Systems
The speaker concludes by summarizing the benefits of using CVA for managing complex components within design systems. They emphasize the improved predictability and structure that CVA brings to the development process. The speaker advises using CVA judiciously for large components with many variants and encourages viewers to share their experiences with the tool.
👋 Final Thoughts and Call to Action
In the final paragraph, the speaker invites viewers to share their experiences with CVA, especially in production environments. They encourage subscriptions and likes and signal the end of the video with a promise to see the audience in the next one.
Mindmap
Keywords
💡Tailwind
💡Class Names
💡Variants
💡Stitches
💡Compound Variants
💡Class Variants Authority (CVA)
💡Design System
💡Prop
💡Specificity
💡Maintainability
Highlights
The complexity of managing class names in Tailwind when dealing with components that have multiple variants.
The use of the library 'stitches' to manage styling in CSS and JS with a structured approach to defining component variants.
Introduction to 'class-variant-authority' (CVA), a library that helps manage Tailwind class names for complex components.
CVA's similarity to stitches in functionality but tailored for Tailwind CSS.
Building a button component from Shopify's design system using both plain Tailwind and CVA to compare workflows.
The challenge of creating impossible variants in a design system to prevent incorrect component combinations.
The process of defining props for a button component, including variants, modifiers, size, and Boolean properties.
The extensive class name generation process in Tailwind and the resulting complexity when dealing with multiple variants.
CSS specificity and the importance of order in CSS files when using Tailwind to avoid unexpected styling.
The advantages of CVA in reducing the need for complex conditional statements and improving code readability.
The potential downside of creating unintended variants when using Tailwind's prop-based styling.
The importance of maintaining a consistent and predictable code structure when building design systems.
CVA's automatic generation of class names based on props, reducing the risk of typos and inconsistencies.
The use of compound variants in CVA to handle complex combinations of component properties.
The suggestion to improve CVA by allowing the definition of modifiers that can be either null or a specific value.
The recommendation to use CVA selectively for complex components within a design system, rather than for all components.
The potential for CVA to enhance maintainability and structure in large-scale component design within Tailwind.
A call to action for viewers to share their experiences with CVA and to support proposed improvements through community engagement.
Transcripts
you know what I still find the most
annoying thing about Tailwind the fact
that you can really create a big mess
when trying to Define all these class
names especially if you have a component
that has a lot of different variants of
itself
take for example a button a button that
can either be a primary button or a
secondary button maybe even a
destructive button the button can be
disabled maybe there's a variation in
size you have a large button you have a
smaller button all of these different
props you pass into that button will
change something in its class names
and and don't get me wrong like it's
it's not a problem of Tailwind because
Tailwind gives you all the freedom in
deciding how you build this
but there actually lies a problem
because it's so open you usually see
that there is a lot of different
approaches people take in this and
sometimes even in one project
and that usually results in quite a big
mess as soon as you have a more
complicated component
so not that long ago when I was still
styling all my components in CSS and JS
I was actually using a library called
stitches to also help in in styling
these things in CSS and JS as well and
if we look at their website you see how
it works what you see here is that when
creating a new component you actually
Define an object called variants and in
this object you can Nest different
properties and each of these properties
eventually will map to a property of
your in this case react component as
well so if you for example add a size
property you see here
by doing that you're actually defining
different sizes this button can have and
based on the value of the prop you pass
in the actual CSS that's nested inside
that object will get rendered so this
gives you kind of a structure to Define
what should change in the styles of a
component when a certain prop gets
passed in and there is one more thing
that makes this even greater to use and
stitches calls that compound variance
because think about it what should
happen if a primary button is also
disabled let's say primary button is
green should it then still be green or
should it become grayish or a more toned
down green color that that totally
depends on design system of course but
these combinations that are usually the
cause of a lot of really weird if
statements in your code to get the right
code because the primary button should
maybe a grayish green color but the
secondary is perhaps a blue collar
that's a bit grayish so there's all of
these different variants in color and in
styles that you need to Define
and something like this hasn't been
lacking in my opinion for for Tailwinds
until recently when I discovered this
Library called class variance Authority
or CVA in short
which does exactly this but for Tailwind
if if we look at their examples on the
GitHub page we see that they're doing
something pretty similar to what
stitches does it as well but they're
instead of rendering Styles rendering
different class names for Tailwind
so what I want to do today is try to
build a shopify's own button component
from their design system and take in a
few variants that they also offer in
their design system like the primary
button or a destructive button and see
what the difference would be if we would
build this in plain Tailwind or with CVA
so let's jump right in and see if this
can improve our workflow so while
looking at shopify's button I noticed
that for every different variant you
select for example primary or
destructive I noticed that they add in a
different property to the component but
also if you for example combine multiple
they're always adding a different prop
to the component this does actually
bring one small downside and that is
that you are able to create variants
that aren't real variants
you could for example make a primary
destructive button which is probably not
something they want to exist but if we
go to 2D code sandbox page you see that
we can actually add in
primary and destructive and I will still
render something albeit not a correct
button probably
so when trying to replicate this
component I actually also changed a bit
of the props around to try to make all
these impossible variants impossible
because most of the time that's actually
a smart thing to do in design system
make all these impossible variants
impossible really weird variants of
components and create things that should
not exist
so if we look at the code I already
wrote as preparation you see that I
actually added a few props to this
button one is variance where you can
pass in four different variants default
primary destructive or monochrome a
modifier which can make a button plain
or outlined a size slim medium and large
and two Boolean properties tool width
and loading and you also can pass in a
native button property called disabled
which will also change something in the
Styles so if we scroll down a little bit
you see also that I added a few defaults
if you do not pass any size then the
size will become medium and the very end
will become default and now we get
actually to the Tailwind class name
generation you see it's a lot but if we
look on the left side you also see that
there is actually a lot of different
variants of this button as well there
are smaller buttons bigger buttons
outline buttons disabled buttons pinning
buttons and that in four different
variants as well so that that already
makes it quite complicated it and then I
actually even tried to to do my very
best to make this as structured as
possible but yeah you have to agree it's
it's a lot so if we quickly look through
this code you see that first I start
with some general styles that are always
applied which are actually not a lot
um and then you see there's different
variants which all set for example a
background color a text color and a
shadow for the default button next to
that we have the modifiers either the
outline which overrides the color to the
current text color and does the same for
the Shadow and for a destructive button
that's outlined we changed the text
color as well for the plane button we've
also overwritten a few styles to remove
a border and make padding a bit smaller
and again for the destructive and
primary button also change the text
color and the same goes for different
size of the button it's mainly attack
size and padding change and for a
disable button also changing the
background color and the text color of
the buttons
so perhaps when someone talks you
through this you're actually thinking
well it's not too bad and it's true but
also take into account that for example
this does not include any hover or Focus
States yet all of the buttons don't do
anything when you hover them and perhaps
there's even more complicated variants
that you also want to combine
and last but not least I knew about all
the variants when creating this
but imagine that there wasn't any
outlined version of the button for
example that could cause you to write
Styles in a different way and then when
you knew about it up front so next to it
is quite tidy way of writing the class
names I also created a different variant
of the same button where you actually
generate the class names in a slightly
different way which is something you
come across quite often but which I'm
also not a huge fan of so if I go to
this buttons not so neatly structured
file you see that I'm taking a slightly
different approach here I am creating a
variable that contains all of the
different class names that we want to
render and later on I'm attaching them
to the class name of the button itself
so in here I'm actually doing a bit more
complicated if statements but the entry
result is actually the same but looking
at this you already see that you need to
take even more time to know what is
actually doing and in my opinion that's
mainly because they're not really
grouped together by what a different
variant of button should do how for
example a primary button should look
like but they are rather grouped by for
example border or background color which
is something that's irrelevant if you
quickly want to scan how a component
would behave in that case it's way
easier to see the full picture of a
certain variant instead of all of the
different variants combined for a single
property and exactly code like this is
what I'm mostly worried about because
this is kind of a bug waiting to happen
because this is not really in my opinion
something that's quickly maintainable
people will easily make mistakes because
you just simply Overlook something
and if we quickly go back to the
previous button there's also already a
bug in this code as well and it maybe
has a little bit to do with the way we
wrote Our code but it's also something
that's good to know when writing
Tailwind because what you see here is
that our plane button has a padding ax
of 2 and a padding y of one whereas the
different sizes also set their own
padding so what is currently happening
is that all these sailing classes are
concatenated together into a long string
so if we inspect this button you see
that this button has both the smaller
paddings from the playing class as well
as the larger paddings from the large
size and the reason that the largest
padding is applied doesn't actually have
anything to do with the order that the
class names are defined because CSS
completely ignores the order the class
names are defined in the class attribute
the only thing that takes precedence is
the specificity of the CSS and in case
of Tailwind as long as you don't do
anything special all of the generic
classes are of the same specificity so
as soon as all of the classes are the
same specificity what the browser will
look at next is the order in which they
got loaded in the CSS file so if one
property comes after the next property
in the CSS file and has the same
specificity then that property will
always be the one that's used so in this
case the padding X of 6 is after the
padding X of 2 in the file so that's why
it's taking precedence so if we for
example change this to adding X of 20
you already see that it changes where
it's still the first padding X that's
defined but it's later in the file so in
case you're using Tailwind is also
really important to actually not Define
duplicate properties because it might
look like it works but perhaps it does
not simply because there's different
orders in the way the CSS is loaded and
it could even be that because of hot
reloading in the dev environment it
actually seems to work but as soon as it
gets built everything gets put in the
correct order and then it will break in
production so it's always good in these
cases to also prevent defining duplicate
properties as well so my advice in this
case would be to copy the padding and
add a different check where we check
that the modifier is not equal to plane
and the size equals slim and only then
apply the different styles this way the
padding will not get rendered if the
variant is plain and then only this
padding will be be applied
so we now can do the exact same thing
for the medium size and large size as
well and with the help of copilot that
seems to be pretty easy actually now we
should not forget to remove the old
pannings from the previous declarations
and with that you already see that our
button actually became smaller
and there is no duplicate padding
properties rendered anymore
so and after we've looked how button
would look with HUD CVA it's now time to
convert it to CVA and see if the code
becomes better in maintainability
structure readability or perhaps not at
all what I already did is create a new
file called button CVA where I've only
added in a basic button the next step we
need to do of course is install class
variants Authority by running yarn at
Clause variance Authority
and when we did that we are able to
create a new button by creating a
variable called button which equals CVA
where we import CVA from class variance
Authority
and as a first parameter to this
function we actually input all of the
shared class names of the button
and the second property of this function
is an object that contains all the
different variants
the variants itself are nested inside an
object called variants
and if we look on the left side I
actually decided to create a variant
called variant to me it feels very
natural to have a button that has a
property variant called primary or
secondary CVA itself usually uses the
word intent for that so decide whatever
suits you best but for now I will stick
with variant and you can add any of
these properties by just creating a new
property inside the object and all of
the keys that you write inside this
object will become valid values for the
button property so in our case that will
be default primary destructive and
monochrome and if we look at our code on
the left side we can actually already
copy in a few of these class names as
well to set the correct styles
monochrome primary and destructive what
I also already did when setting this
project up is also create this overview
page where you can see all the buttons
but next to the default button I also
already added the placeholder for the
CVA Button as well so if we go to the
app.tsx you see that there's one section
that has a default button and the second
section that has the CVA button where I
actually already render all of the
different variants
so what we can do now is instead of
importing them from Button as well which
I did as an initial setup actually
imported the CVA variants from button
CVA and what you will immediately see is
that we are getting some type errors
because right now we don't have any
other types than children Define but
that's something we will fix down the
road
so if we now go back to the preview page
you see that there's no Styles applied
yet and that is of course because right
now we are only defining this object but
not doing anything with it so what CVA
does is this button is actually a
function that you can set as a input for
the class name so if we run this
function it will return a class name
String based on the different props of
the button or actually not based on
different props of the button but based
on the arguments you pass in which
should be the different props of the
button so the next step in order to get
that to work is to extract all the
different props from the button and
input them into that function and if we
look at our default button we actually
defined all of those manually however
CVA gives us the option to do this
automatically based on the object we
defined here which is actually really
neat because that way it's also
impossible to create a typo for example
and create a property that does not
exist or is not used in CVA we can do so
by actually extending our button props
also from variant props where you pass
in type of button and if we pass in this
variant props type of button it will
automatically add the variant prop to
our button props type
so this way we can extract the variant
and already insert it into the button
function so if we now go back to our
button we see already that the colors
are applied of course that's not
everything but that's simply because we
did not move over all of the different
properties yet so let's do that next so
to keep track of what we already copied
in and what not I will actually add a
comment in front of all the different
things we moved over because the next
step is actually the different modifiers
so that means that we need to create a
new modifier variant in here which can
either have a value of outline and look
at copilot already being this smart that
it knows that I actually want to move
the value from the left side over to the
right side that's
so let's just auto complete that and
also add in the plane variant where we
also copy these values over and then we
comment in these two lines and for now
we are actually leaving the destructive
and primary outlines for a second
because we're actually going to use
compound variants down the road for this
the next variant we of course have is
size where we have slim which only has
text M medium which is the same and
large which has text base next up is a
full width although instead of a string
this is actually a Boolean but CVA also
gives you the option to render a Boolean
true or false in here
and finally we do the same thing for
disabled which also is a Boolean
property for which we copy these values
in and while we copied and disabled what
happened down here is that an error
popped up because it's actually
complaining that there is already a
property disabled in the button HTML
attributes and that disabled property is
actually different from the Boolean we
set here because instead of a Boolean it
can also be null or undefined in the
default react types so since we only
wanted to be a Boolean it's actually a
good thing to let the variant props take
precedence over what is in the button
HTML props and we can do so by actually
adding omits around the button HTML
properties where we can omit the
disabled property and if we do that the
button props is happy again of course we
should not forget to extract these
modifier size full width and disabled
variants from the props as well and pass
them into the button function and as
soon as we've done that we already see
that we're a little bit closer to where
we want to be the main things missing
now are these compound variants so let's
add those in next as a second property
to the root object we can Define the
compound variance which takes an array
of different compound variants you want
to apply specific societal so the first
one you see which we skipped is the
outline modifier and destructive variant
we can style them by adding modifier
outline and variant destructive and you
see already that copilot is super smart
again and knows that we want to add a
class name text destructive to this and
that's actually the only thing we need
to do CVA is now smart enough to look at
these different variants that we pass in
and see if this class name should be
applied as well because both of these
variants are active we should do the
same thing for the primary outline of
course modifier outline variant primary
and class name text primary and we also
have some variants for the plane
modifier that we want to set where
modifier equals plane variance
destructive and the text becomes text
destructive again and the same thing for
the primary variants that leaves two
more which are firstly the paddings for
the different buttons when the modifier
plane is not applied so we can add them
again by setting modifier to null first
which means there is no modifier passed
in and the size slim and the padding and
the similar thing should also happen for
the modifier outline and then we
actually need to copy this object two
more times to add in the medium size in
the padding as well as the large size
ice
and what in my opinion would actually
still be a really nice Improvement is If
instead of defining multiple modifiers
here where we need to say should be null
or outline we could for example say to
be either null or outline
this way instead of creating two
different compound variants we could
only Define one or just say it should be
not plain and by doing that it would
make it way more organized and we would
have a lot of less duplicate code which
again makes this better maintainable and
a lot easier and quicker to scan and if
you do support that check out this pull
request I created and give it an upfld
as well because I think it's really
useful and in my opinion it would make
this tool even better so let's convince
Joe together
but all jokes aside let's for now remove
this modifier again and add the outline
in here and after we've done that
there's only one compound variant left
which is disabled true and variant
default which should add a different
border on there and finally we can add
one more property which is default
variance and inside default variants we
can say that the variant should be
default and the size should be medium
this way we don't need to always specify
the default variance to a button and
these Styles would will get applied by
default and as soon as we've done that
saved it and go back to our components
we see that we have all the variants
back the only variant missing still is
loading variant but if we look at the
all buttons code you actually see that
this does not add any class names to the
component itself rather it manipulates a
few child components so let's quickly
copy that over as well since we can just
replace all the children with this HTML
and then we need to make sure that we
add the loading Boolean in here and this
loading Boolean should be added to the
button props itself because it's not
part of the CVA configuration since it
does not add any Styles so we can just
add it in here as a Boolean and then
also destructure it from the props and
by that we should have a loading button
and look at that we do so as a quick
summary it might be good to put these
two side by side one more time so let me
quickly uncomment the lines again and if
we put them side by side you see that
there's actually quite a bit of a
difference
because to me the CVA side looks quite a
bit more structured
whereas on the left side
is pretty neat especially if you if you
think about the not so neatly structured
version I showed you before which is
also still an alternative we should not
forget because this is the type of code
you see in production as well but it
still feels that on the right side it's
a lot quicker to see what different
types of variants there are although on
the left side you could of course also
look at the interface
but you need to scroll a lot up and down
left and right to to really get a
complete picture where with CVA you
immediately see what different variants
there are and what type of styles they
apply the only downside right now is
that you need to to Define quite a few
of compound variants and especially
quite a few duplicate compound variants
to override class names whereas on the
left side you can use a bit more
JavaScript like does not equal for
example which is not offered by TVA yet
but I really hope they will implement
this in the future because as soon as
they do that that also ensures that
these compound variants drop in half or
maybe one third which in my opinion
would make this a breeze to use and not
per se because it's maybe a better
overview but mostly also because it
forces you to write your code in a
certain way so that also means that
everyone will write their Styles in this
way I cannot add a little if statement
somewhere in between and do something
just a little bit different use a
ternary if statement in in one place and
maybe use a function to generate a class
name because it's more complicated if
you use CVA everyone will do it in the
exact same way which makes your code way
more predictable for for a developer to
know where to find certain stuff and how
things should work
so in my opinion it definitely makes
sense to use CVA for for bigger
components especially within Design
Systems where it's really important to
create these tight boundaries and how
components should work how they are
structured and also to make sure that
you don't break one of the five variants
when you change something else what I'm
definitely not recommending is creating
all your components with CVA only use
this tool when it makes sense if you're
building a very huge design system or
have a really really complicated
component that has a lot of different
variants within it CVA is not a library
that's really big in file size so it's
definitely something you can only use
within a few components and that should
as long as you're not building a full
design system absolutely be the way you
you're using this should not be the
default for every component because that
makes it just Overkill and takes away
all of the benefits of Tailwind for
quickly writing components and just
iterating really quickly because you're
adding a tool in between
you should only edit when it makes sense
and that is when you have a component
that has a lot of different variants so
I hope with this video I was able to
show you how you can create better
Design Systems within Tailwind as well
without losing your mind with all the
class names and ternary operators and
everything that otherwise would be
involved in this in my opinion CVA is
really a blessing to use when you're
building really big components so I hope
you enjoyed this one and let me know if
you've used it in production anywhere
and what your experience is with it
please subscribe leave a like down below
and I'll see you in the next one
Weitere ähnliche Videos ansehen
Explaining Figma Components Like You’re Five (Simplest Way Possible)
Why I Pick ShadCN and Tailwind for all my projects
Flowbite Crash Course in 20 mins | Introduction to UI components using Tailwind CSS
¿Que es Tailwind CSS?
Customizing Angular Material just got easier in v18!
Comparing Modern CSS Solutions (Tailwind vs MUI vs Bootstrap vs Chakra vs...)
5.0 / 5 (0 votes)