A lib to create and extends React components defining variants like Stitches using Tailwind!
Summary
- Installation
- Basic Usage
- Heritage
- [Experimental] Deep Classes
- How it works
- Classes Priority
- Snippets
- Bug Fix
- Roadmap - big news!
Installation
To install Tailwind Factory you need to run in your project:
//Using pnpm
pnpm add tailwind-factory
//Using npm
npm install tailwind-factory
//Using yarn
yarn add tailwind-factory
Tailwind Configuration
If you want to use with Tailwind you need to install and configure Tailwind
before!
To use Tailwind CSS IntelliSense
you need to add the following configuration in your User Settings:
//Tailwind IntelliSense Regex
"tailwindCSS.experimental.classRegex": [
["tf\\(([^)]*)\\)", "(?:`)([^'\"`]*)(?:`)"], // tf(`...`)
["\\.extends\\(([^)]*)\\)", "(?:`)([^'\"`]*)(?:`)"], // xxx.extends(`...`)
],
Basic Usage
import { tf } from "tailwind-factory";
//Example of a common use case
//Note: use ` to use Tailwind CSS IntelliSense
// and " or ' to use the properties' autocomplete
export const Container = tf("div", `
flex
flex-col
`, {
variants: {
theme: {
dark: `bg-zinc-800 text-zinc-100`,
light: `bg-white text-zinc-800`
},
size: {
md: `w-full h-[200px]`,
lg: `w-full h-screen`
},
centralized: {
//These keys (true and false) are reserved for boolean values
// or their numerical value (0 and 1)
true: `justify-center`,
false: `justify-start`
}
},
defaultVariants: {
size: "lg",
theme: "light"
}
});
Use case:
<Container centralized>
<p>Now you can use it as you wish</p>
</Container>
Custom components
Tailwind Factory also support custom components:
//Example using a custom JSX component
const JSXTitle = (
//The component need to have the className property!
{ children, className }: {
children: ReactNode,
className?: string
}
) => <h2 className={className}>
{children}
</h2>;
//Is recommended create the component outside the function
// to prevent a bug with Tailwind CSS IntelliSense
export const Title = tf(JSXTitle, `
text-3xl
text-inherit
`, {
...
})
Heritage
Components receive a function called extends
which can be called by passing or not a new component. The first parameter is precisely this new type of component. If null
, it will inherit
the extended component. Otherwise, it will inherit
all properties and variants of the new component.
//Example extending the styles
//Note: all factory components have a `extends` function
export const Header = Container.extends(
null, //Will inherit the properties and variants of Container
`
flex
justify-center
items-center
w-full
`, {
variants: {
theme: {
dark: `bg-zinc-800`,
},
border: {
true: `border-b-4 border-zinc-600`,
false: ``
},
size: {
sm: `h-[20%]`
}
},
defaultVariants: {
//theme: "light", is not necessary
border: true, //can be a string
size: "sm"
}
});
You can replace the null value with another component
:
//Example extending another component
export const Header = Container.extends(
//Will inherit the properties of AnotherComponent
// and variants of Container
AnotherComponent,
`
flex
justify-center
items-center
w-full
`, {
variants: {
...
},
defaultVariants: {
...
}
});
The idea was to make it closer to the 'as'
property provided in some libraries. I won't go into details, but I failed to obtain this result and this was my way of mimicking
this property.
I'm still wondering if the best way is to keep the extends function together with the components. If you have a problem with this or an idea, you can create an Issue.
[Experimental] Deep Classes
In some cases, to avoid creating multiple components
you can use a syntax similar to CSS
:
//Deep classes example
const Container = tf(
"div",
`
bg-lime-200
w-4
h2 {
italic
}
div {
h-3
}
> div {
flex
flex-col
bg-blue-200
> h2 {
font-bold
}
h2 {
text-6xl
}
}
> h2, h1, p {
text-red-400
}
`);
Example of component structure:
<Container>
<h1>Red Title</h1>
<h2>Red</h2>
<p>Red Text</p>
<div>
<h2>Normal</h2>
<div className="hover:bg-red-300">
<h2>Normal</h2>
</div>
</div>
</Container>
Example output:
<div class="bg-lime-200 w-4">
<h1 class="text-red-400">Red Title</h1>
<h2 class="italic text-red-400">Red</h2>
<p class="text-red-400">Red Text</p>
<div class="h-3 flex flex-col bg-blue-200">
<h2 class="italic font-bold text-6xl">Normal</h2>
<div class="h-3 hover:bg-red-300">
<h2 class="italic text-6xl">Normal</h2>
</div>
</div>
</div>
Available syntaxes
To inject by tag
:
div {
bg-red-500
h1 {
text-gray-200
}
}
To inject by class
:
.hero {
bg-red-500
h1 {
text-gray-200
}
}
On inject by class expected classes
are saved
, but are sent to the beginning of the class list. It is understood, in this case, that the expected classes
cannot overlap with other classes and variants of Tailwind Factory.
To inject by id
:
#hero {
bg-red-500
h1 {
text-gray-200
}
}
To inject into multiple
:
#hero, section, header, .title {
bg-red-500
h1 {
text-gray-200
}
}
To inject only in the first group
of children
inside the component (support multiple
syntax):
> div {
bg-red-500
h1 {
text-gray-200
}
> .main, input {
rounded-md
}
}
Unavailable syntaxes
First, this deep class
approach is not the same as defining classes in a typical style file
! Some things like checking states is not supported. Example with :hover
:
//not work!
div:hover {
h2 {
text-red-500
}
}
This happens because Tailwind Factory
only works with class management
! You can get around this by defining your classes in a styling file
.
Tailwind Group
In some cases, a group
in Tailwind is the sufficient to set up a hover
:
//work!
div {
group
hover:bg-gray-500
h2 {
group-hover:text-red-500
}
}
In other cases you may prefer to use external classes:
//style.scss
//With Tailwind (you can use the common CSS too)
.custom-class:hover {
h2 {
@apply
text-red-500;
}
}
//work too!
div {
hover:bg-gray-500
custom-class
}
Using with variants
The variants
support deep classes
:
const Container = tf(
"div",
`
bg-lime-200
w-4
`, {
variants: {
italic: {
true: `
h1, h2, h3 {
italic
}
a {
no-underline
}
`,
false: `
h2 {
underline
}
`
}
},
defaultVariants: {
italic: false
}
});
You can extends
too:
const Hero = Container.extends(null, `
h1 {
text-9xl
}
`);
How it works
Tailwind Factory just arranges
the classes within the variants according to the properties passed for the component. Tailwind does the rest, so I think you'll have no problem using other forms of styling based on class
definitions. Like traditional CSS
, Sass
, or CSS Modules
.
Come to think of it, maybe the name should be Style Factory or Class Factory. But now it's too late... I will keep the name.
Note: Classes are formatted before being passed to components. Reducing the number of spaces between classes to one.
Classes Priority
- Inline Classes
- Factory Variants
- Extended Factory Variants
- Factory Styles
- Extended Factory Styles
- Inline Saved Classes
- Inline Classes used in Deep Classes
Snippets
Tailwind Factory has an official extension that accompanies some snippets. See in: Tailwind Factory Extension
List of Snippets:
Documented version: 0.1.0
tfi
: Import Tailwind Factory and create a new factory component
import { tf } from "tailwind-factory";
export const Container = tf("div", `
`, {
variants: {},
defaultVariants: {}
});
tfc
: Create a new factory component
export const NewComponent = tf("div", `
`, {
variants: {},
defaultVariants: {}
});
tfe
: Create a new extended factory component
export const NewComponent = Parent.extends(ParentComponent, `
`, {
variants: {},
defaultVariants: {}
});
Bug Fix
First, sorry for the inconvenience.
There was a bug that I hadn't noticed that affected the rendering of elements that had a string as a child. It was happening because React passed the element's children as an object, and back in the tests that didn't happen with strings! I discovered, then, the existence of a function that converts the children to the expected format, the: React.Children.toArray
.
Roadmap
-
Generate styles to provide further support and optimization (with support for SSR).
Unfortunately I will have to change a lot of things about Deep Classes, but this may be essential so that I can expand the functionality of Deep Classes.
I will try to maintain the independence of tailwind and will not change the way other functionality not linked to Deep Classes works. It will take a lot longer to develop, so be patient!