-
-
Notifications
You must be signed in to change notification settings - Fork 101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Component Proposal #73
Comments
Looks like a good approach, and compose sounds like a reasonable name! Something to think about is offering a way to customize the generate class names, e.g. prepending a string or running some kind of transformation function. Styled components has a handy babel plugin that automatically prepends the component name and/or file name before generated hashes so you can easily debug styles. I don't think twind could achieve this same experience without Babel, but in the meantime a way to manually add this info might be useful for debugging, e.g. compose('PurpleButton')`py-4 px-8 rounded-lg bg-purple-600`
// -> .PurpleButton-XXXX |
Emotion uses labels for that. let style = css`
color: hotpink;
label: some-name;
` If twind would support labels, or className prefixes, we could do something similar. That way it could even work when adding labels inside the className with the shim. pseudo:
And
The separator could be either a |
I like the label idea and would move that to separate issue. |
The one thing I'm hesitant about is adding more complexity to the public interface. This proposal might raise more questions around "when I should use Is there a more direct solution to making overrides more reliable? I'd prefer that. I think the current composition model is lovely, it's just that the lib's approach of outputting tailwind class names is at odds with the cascade. That's one of the big wins to using Emotion and others, that you pretty much never have to worry about correctly applied overrides. One naïve idea that comes to mind, is generating extra CSS classes with a suffix like tw`text-blue-500 text-red-500`
// outputs text-blue-500 text-red-500
tw`text-red-500 text-blue-500`
// outputs text-red-500 text-blue-500-override-1 I have a feeling this would be a pain to make work 🙃 just throwing things out there |
Reading this makes me wonder whether or not we can merge functionality of tw and compose to avoid adding additional API-surface, users could get confused about when to use what and a huge selling point here is that 'you just need tw to start'. My thinking would be that when we invoke tw and encounter an existing class we could consider that as the base and the added ones as extensions on the base. const base = tw'bg-blue'
const extended = tw`${base} bg-red` So this would be one approach, this would enforce the user for generic components to stringify base and add additional classes as a prop. Another option would be to consider the lexical ordering, so when we encounter bg twice in the same interpolation we could forget about the first encounter or make the second one I personally think the combination of the above two options could turn out pretty strong. Just throwing some idea's out there, I do think the idea in itself is sound but I'd look for options not to introduce more API surface. Do note that this limitation also currently exists in Tailwind and often gets the advise of making the 'stable' overridable styles as props for instance a button flavor,... |
I agree it would be super nice if we can have these two concepts in one ( There is a piece I'm missing, too, though... Keep in mind im using web components, if someone does this: html`<div class=${tw`bg-blue`}></div>`; // results in class="bg-blue"
const base = tw`bg-blue`;
html`<div class=${tw`${base} bg-red`}></div>`; // results in class="bg-blue bg-red"? surely the simplest route would be to follow order, and to know so your internal implementation of also, in tailwind itself if you had maybe im just missing something or skipped an example |
@43081j in the current implementation the order of which the .bg-blue {
background-color: blue;
}
.bg-red {
background-color: red;
} .bg-red {
background-color: red;
}
.bg-blue {
background-color: blue;
} The above two stylesheets will lead to different results when it comes to which class gets applied to the dom-element. I could be mixing it up but for This is a limitation within css since there's no inspection what a class actually does, in JS-land this should be solvable. |
I understand that part, but to solve that could we not just deal with overrides when we call for example: tw`bg-blue bg-red`; // bg-red
tw`${tw`bg-blue`} bg-red`; // bg-red
tw`bg-blue`; // bg-blue as it doesn't seem "normal" that anyone would ever purposely want two conflicting classes. i assume the only reason anyone would ever have conflicting class names would be if they're trying to compose or have done it by accident. in which case, we could just de-dupe for them and drop the overridden classes like above. this way the CSS would be what it is now, and would only ever contain the tailwind classes (not our own extra 'generated classes'). right now, twind pretty much seems to output the same css as is inside tailwind itself. but if we do this proposal, that wouldn't be true anymore as it would also contain a bunch of our own generated classes. |
Yes, your above comment is a good elaboration on
You'll often see the pattern return where for instance an extendable style has this property called flavor. <Button flavor="primary" /> Which will determine these overridable attributes, there are often special cases though when it comes to margins/paddings/.... so I can see the merit there, ran into a few of those myself where a composable component would need a bit more margin, .... |
that could still be solved by de-duping conflicting classes though, right? just wondering if we don't need to go as far as producing our own CSS, and can instead control the class names more to ensure conflicting ones are removed. again, i could be missing something though. if there's a case where that wouldn't work, it'd be useful to know |
Well it depends, right? You would need to be able to inspect classes to see what it contains which is a lot more complicated than it sounds.
This needs a reverse-translation step to see what |
Not having to generate CSS-in-JS objects in order to compose (i.e. just class name inspection) is desirable and have considered the possibility before, however I get stumped at this cases like this: const base = tw`text-center text-xl`
const ex = tw`${base} text-sm` In cases like @JoviDeCroock posted above it might seem trivial to dedupe the class names based on the common Once both these rule sets have been compiled (and if they were stored internally as CSS-in-JS objects) const composedStyles = {
...({ textAlign: 'center', fontSize: '2rem' }),
...({ fontSize: '1rem' })
} By nature of object extension the latter Anyway, I am excited by all the possibilities listed here still. Just thought I'd share the context I have in this problem space! |
i still don't fully understand why this is non-trivial. i get that is the difficult part keeping track of what overrides what? @lukejacksonn do you have an example maybe of where this wouldn't be so simple? the JS representations make it very easy to compose, but it'd also be very nice if we could only deal with class names, making our output CSS almost equivalent to what tailwind itself would have. |
Thank you all for your input. I try to address these here and will update the initial post.
The problem is, sorry for not being more clear in the problem statement, that it is really difficult to know which directive does override another. Lets stick with
This ambiguity makes class based composition really difficult. That was the reason we introduced the tailwindcss and twin-macro both use style composition which merges the css properties which would solve this problem for most cases. I still think that style composition is the best possible way right now to solve component composition while keeping utility classes for all other case.
I totally agree that the addition of
I think that |
ah i get you now, thanks for the great explanation 👍 not all so really this is an issue tailwind themselves also have (technically)? what this means is we'd no longer be an implementation of tailwind, as we are now, but would also have our own feature set on top of that (composition). this library would diverge to be "tailwind and more". thinking about it, i think personally i would argue that it remain "dumb" and just append the styles as it does now, maybe in the same order tailwind has them at least. so if that is my opinion then i suppose im in agreement with two functions ( otherwise, HTML written using tailwind classes might behave differently using this library than it would using tailwind itself. |
Here's an idea. This is probably somewhat of a sin in TS land (and in general), but what if we attached properties to the resulting string from const twMeta = Symbol()
type TwMeta = { cssProperties: {} }
interface String {
[twMeta]?: TwMeta
}
const className = tw`bg-blue-500`
className[twMeta] // { backgroundColor: "blue", "--tw-opacity": 1 } Then, if the First issue that comes to mind is the possibility of losing that metadata attachment in transport somehow, but I'm not sure if/how that could happen. I feel like that'd only happen if the user's trying to do something really clever. I guess the goal is just to have some metadata associated with each generated class name string somewhere, so then the |
To approach from another angle, I would solve this at import { setup } from "https://cdn.skypack.dev/twind/shim"
const btn = `
py-2 px-4
font-semibold
rounded-lg shadow-md
focus:(outline-none ring(2 indigo-400 opacity-75))
`
const btnIndigo = `${btn} bg-indigo(500 hover:700) text-white`
setup({
...
aliases: { btn, btnIndigo }
}) Then, in code you would refer to the aliases through In practice, I've found that having composition alone isn't enough and you need a proper component model to have a complete abstraction but for simple ones something like here would be enough and I believe it would replicate Tailwind's The caveat of my approach is that now you are forced to use |
This is already possible using plugins: setup({
plugins: {
btn: `
py-2 px-4
font-semibold
rounded-lg shadow-md
focus:(outline-none ring(2 indigo-400 opacity-75))
`,
btnIndigo : `btn bg-indigo(500 hover:700) text-white`,
}
}) While trying to link to the docs i noticed that this is missing. Sorry for that. I'll try to add this later. |
If we do not want to introduce const button = tw`bg-gray-500 rounded`
// => bg-gray-500 rounded
const redButton = tw`${redButton} bg-red-500`
// current implementation => bg-gray-500 rounded bg-red-500
// what we want => rounded bg-red-500 This should be the injected css: /* No .bg-gray-500 injected */
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
}
.rounded {
border-radius: 0.25rem;
} As @JoviDeCroock has suggested this needs some way of reverse translation and maybe (optional) making style injection lazy like we already do for 1. Lazy Injection (optional) Currently styles are injected into the sheet during the evaluation of the To work around that we could switch to deferred injection of styles:
2. Reverse Translation This is the tough one. What i imagine could work would be something like this:
Step 1 and 2 are possible. Step 3 may have some edge cases like what to do if the css is a partial match: .bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
}
.bg-opacity-5 {
--tw-bg-opacity: 0.05;
}
Another edge case may be if the I think first-class composition would be great but i'm unsure if it could be implemented in a reliable, performant way. |
The ease of implementation on the part of I'll also point out that the requirement of generating vanilla tailwind classes is at odds with trying to nail down a good UX here. We're falling victim to the CSS cascade 😕 |
just to take a step back again, do you have an example of when someone would need this? sorry to keep coming back to this. but lets say tailwind's own css defines this: .bg-blue {
background: blue;
}
.bg-red {
background: red;
} with tailwind, if you did this isn't really a "problem" because its your own fault for specifying two backgrounds. so whats a real use case of when you'd purposely want to do this? for example: const base = tw`bg-red`;
tw`${base} bg-blue`; we would right now produce presumably the use case here is when you want to do this kind of composition and have the stylesheet ordered by your input rather than what we decided initially. if thats the case, i'd still take the hit on API size to keep this extra functionality separate. again, if anything, i'd order rules the same as tailwind somehow (when using edit: i suppose its not so bad/misaligned as long as this new behaviour only happens to nested |
The use case to allow user of a component or component author to override some styles (the example is in react but it applies to other frameworks aswell): const Button = ({ className, children}) => {
return <button className={tw`bg-gray-500 text-base ${className}`}>{children}</button>
}
const ButtonBlock = ({ className, children}) => {
return <Button className={`block ${className}`}>{children}</Button>
}
<Button>gray-500</Button>
<Button className="bg-red-500 text-lg">red-500 large</Button> |
Yeah, this is pretty much where I'm at. I think the extra power and peace of mind is worth misalignment in some cases
It does, and the best way I've seen to solve it is to not have class overrides, or in other words, only include the classes you want. So instead of |
ok yeah the case makes more sense now. IMO then i would aim for that kind of api:
to aim for this: tw`bg-red bg-blue`;
// css .bg-red {}, .bg-blue {} are appended
// result is bg-red bg-blue
const base = tw`bg-red`;
// css .bg-red {} is NOT appended as it already was on line 1
// result is bg-red
tw`${base} bg-blue`;
// css .generated-bg-blue{} is appended
// result is bg-red generated-bg-blue where the last one is tough to detect as you need a map somewhere, or some cache, to know "this string is a nested tw result". edit: something logically like: function tw(strings, ...expressions) {
if (expressions.some((expr) => exprIsTw(expr))) {
// here we somehow key/hash this call's args
// and generate a new css class with it, which we
// prefix all the tailwind classes in `strings` with
// which came after a `tw` expression.
// we then append those classes if they haven't
// been already (we'd know because its a hash/key in a map)
} else {
// do as normal
}
} |
I have adjusted the proposal text to outline the problem, explain our requirements and show several possible solutions. Please continue to discuss so that we find a viable solution. |
I think what @43081j suggests is great, iirc there is already a rules-to-className cache or what that only in oceanwind? If there is one we could probably take
The code provided and translate classNames back to their corresponding tw string, this allows for reuse and presedence checks. |
Thanks for the update. Nested
Not sure if this is possible, or if there are potential issues with this, but maybe you could keep two DOM stylesheets, one for the regular TW rules, and one for the generated rules, so the generated rules would always be in the DOM after the normal rules, therefore having higher precedence. Another option would be to specify the generated rules twice, so |
Is a part of this proposal to solve the issue shown here? https://codesandbox.io/s/flamboyant-water-pd4xl?file=/src/index.js There's no nesting or anything, this is just hitting the cascade. Might be unrelated, but I wonder if there's a way to make this work with class name order. |
That is normal tailwindcss behavior. |
We already have a presedence order and can control in which order css rules are inserted in the stylesheet. My problem is how do we solve this for arbitrary depths: // Somewhere in the app
tw`bg-green`
tw`bg-blue`
tw`bg-red`
// => order in stylesheet: .green{} .blue{} .red{}
// Later in the app
const red = tw`bg-red`;
const blue = tw`${red} bg-blue`;
// => should be blue but is red because .red{} is later in the stylesheet
const green = tw`${blue} bg-green`;
// => should be green but is red because .red{} is later in the stylesheet |
I'm trying to wrap my head around how solution 1. Nested tw could be implemented. Some issues i see: const base = tw`bg-red`;
tw`${base} bg-blue`;
tw`bg-green ${base} bg-blue`;
tw`${base} bg-blue`; vs tw`${"bg-red"} bg-blue`; |
I'm still in favor of putting this into a separate module because
|
I have been thinking about it, and comparing it to how lit-element works in fact. In lit-element, the way they know if an expression is another template is by having a const base = css`xyz`; // TemplateResult instance
css`foo ${base} bar`; // detects that `base` is a TemplateResult and does any merging, etc and in my proposed solution, having some so it probably does make sense to have a const base = tw`foo`; // this is a string now
tw`x ${base} y`; // would be "x foo y" because it is string interpolation
tw`x ${compose(base)} y`; // would be whatever our generated classes are, because we're explicitly telling it to it would be super nice if it "just worked" via if you can keep the implementation in the base the same roughly, and all of this i think a primary goal should be to keep |
If tailwind alignment is important, then |
That would be possible with |
I really like the inline compose example.
Me too.
I agree. |
Right, I mean without an entire separate API to accomplish it, having |
😄 For you. I think (hope) most css and tailwind users are aware that the order of class names in the class attribute does not matter. We would change the expected behavior. It should be possible with the new api to do exactly what you want: // order-sensitive hash; x = the new api
const osh = (...args) => tw(x(...args))
osh`bg-red bg-blue`
// => tw-red-blue
osh`bg-blue bg-red`
// => tw-blue-red |
This seems to be a really though one. Thanks for all the thoughts and ideas.I think we now agree that the problem is component based composition and tailwind has a component concept using @apply which basically merges the css rules of several tailwind classes into one class. twin.macro does the same. I would say we change our perspective to view this as a component definition problem.
// Using x as an placeholder for the name
const btn = x`inline-block bg-gray-500 text-base`
// => generates on css class with all declarations of the above rules when used
const btnBlick = x`${btn} block`
// => generates on css class with all declarations of btn & block
// Never used => never injected
<button class={tw`${btn}`}>gray-500</button>
// => tw-btn
<button class={tw`${btn} bg-red-500 text-lg`}>red-500 large</button>
// => tw-btn bg-red-500 text-lg Lets take a look at the available APIs in twind and their transformations:
When i look at this i see a missing piece:
That API needs to
Could we agree that such an API would be useful? Naming this functionality is difficult here are some ideas:
|
This Very Scientific Poll™️ convinced me otherwise! And I learned from this very tweet that class name order in HTML didn't matter 🙃 https://twitter.com/mxstbr/status/1038073603311448064 All in all, I feel like this is going to be a bit of a surprising footgun for people coming from twin.macro, or from CSS-in-JS in general. I'd even argue that not having to worry about the cascade is sort of a CIJ staple at this point.
This is good. Definitely add this to the docs as a recommended way of avoiding the cascade
I like this, it follows the same logic as vanilla TW: creating a new utility class from existing ones. |
Here are some examples using Basic usagePlease note that the utility classes are always defined after the component styles which allows them to overrides certain styles. import { tw } from 'twind'
const btn = tw.apply`
py-2 px-4
font-semibold
rounded-lg shadow-md
focus:(outline-none ring(2 indigo-400 opacity-75))
`
tw`${btn} font-bold`
// => .tw-btn .font-bold
// CSS:
// .tw-XXXX { padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 1rem; padding-right: 1rem; font-weight: 600; ...}
// .font-bold { font-weight: 700; }
const btnLarge = tw.apply`${btn} py-4 px-8`
// Result: () => ({ paddingTop: '1rem', paddingBottom: '1rem', paddingLeft: '2rem', paddingRight: '2rem', fontWeight: '600', ... })
tw`${btnLarge} rounded-md`
// => .tw-btn-large .rounded-md
// CSS:
// .tw-btn-large { padding-top: 1rem; padding-bottom: 1rem; padding-left: 2rem; padding-right: 2rem; font-weight: 600; ... }
// .rounded-md { ... } twin.macro and styled-component compatibility eg generate one classThe would be possible as the returned function has <button className={tw.apply`bg-red bg-blue`}>blue</button>
// => tw-red-blue
document.body.className = tw.apply`bg-blue bg-red`
// => tw-blue-red Or use this helper: // There is a better name out there somewhere
const twind = (...args) => tw(tw.apply(...args))
<button className={twind`bg-red bg-blue`}>blue</button>
// => tw-red-blue
document.body.className = twind`bg-blue bg-red`
// => tw-blue-red `css` can be used within `tw.apply`const btn = tw.apply`
py-2 px-4
${css({
borderColor: 'black',
})}
` Using within
|
I'm dealing with a family emergency right now so I'll be offline for the next week or so. I'll have to catch up on all of this when I get back, but great ideas and feedback from what I have seen so far! |
I have update the propsal text to reflect the idea from #73 (comment). @gojutin I hope your family will be fine and you find the time to be with them in these trying times. |
It would be great if you could give me a 👍 if you are happy with the proposed solution. Please leave a comment if you are not. /cc @tw-in-js/contributors |
Hey guys, I'm sorry bother you. I started using the // import { apply, theme} from 'twind'
// twind/css re-exports apply and theme for convenience
import { css, apply, theme } from 'twind/css'
const prose = css`
/* Extract CSS from these Tailwind rules */
${apply`text-gray-700 dark:text-gray-300`}
/* Combine with CSS declarations */
p {
color: rebeccapurple;
background-color: ${theme('colors.gray.500')};
&:hover {
${apply`text-purple-700`}
}
}
/* Define global styles */
:global {
a {
/* Access theme values */
color: ${theme('colors.blue.500')}
}
}
` I already pushed it to main and released v0.13.0 with the new API. I hope that is ok with you all... I try to do better next time. |
This does make sense to me, for what it's worth. Feels like a good set of tools, very reminiscent of native Tailwind functions + Emotion css syntax. I dig it. I'll be playing for sure! |
I'll do some experimenting with the new API this week and report back, but it seems very intuitive from your examples. |
While working on
twind/styled
(PR #7) i realized that twind does not have a good component composition model. With component composition we mean re-using styles for several components while allowing to override certain styles like changing the background color.Problem Statement
The problem we try to solve is component based composition while
tw
should keep the expected tailwind behavior.One way to do composition is utility combinations to recreate the same component in many different places (see Extracting Components). I would call this class composition as it applies or groups several class names for a component.
Details with an example and its problems
The example above does not reliably work because the injected css classes have all the same specificity and therefore the order they appear in the stylesheet determine which styles are applied.
It is really difficult to know which directive does override another. Lets stick with
bg-*
but there are others. Thebg
prefix and its plugin handle several css properties wherebackground-color
is only one of them.background-color
:bg-current
,bg-gray-50
, ... (see https://tailwindcss.com/docs/background-color)background-attachment
:bg-local
, ... (see https://tailwindcss.com/docs/background-attachment)--tw-bg-opacity
:bg-opacity-10
, ... (see https://tailwindcss.com/docs/background-opacity)This ambiguity makes class based composition really difficult. That was the reason we introduced the
override
variant.Consider the following
twind/styled
(PR #7) example:As you see it is difficult to override certain utility classes on usage or when creating a child component. For this to work twind introduced the
override
variant which increases the specificity of the classes it is applied to. But what do you do for a grandchild component or if you want to override thePurpleButton
styles?override:override:...
? There must be a better way to solve this problem.tailwind has a component concept using @apply which basically merges the css rules of several tailwind classes into one class. twin.macro does the same.
That is something I would call style composition and is currently not available in twind.
Details of tailwind @apply
Tailwindcss provides @apply to extract component classes which merges the underlying styles of the utility classes into a single css class. That is something i would call style composition and is currently not available in twind.
twind.macro does the same during build time to generate css-in-js objects which are evaluated with a runtime like emotion or styled-component:
Lets summarize both composition approaches:
Proposed Solution
Recap of available APIs in twind and their transformations:
tw
: one tailwind rule => one class name – with side effect of inserting the css into the stylesheetcss
: css rules => one class name (via tw) – lazy evaluated (injected by tw on first use)When i look at this i see a missing piece:
tw.apply
: several tailwind rules => one class name (via tw) – lazy evaluated (injected by tw on first use)All considers names
tw.apply
=> to mirror tailwindcss @applycss.of
=> as it create one big css object basicallytranslate
=> as it translate tailwind rules to an css objectcompose
=> as it merges tailwind rules togetherThat API needs to
generate one style object eg one css class combining all tailwind rules by deep merging rules in order of declaration
allow utility classes applied on the same element override its styles; eg styles are injected after base (preflight) and before utility classes
can be used with
tw
=>tw(tw.apply(...))
; eg implement as an inline directiveallow to inject the styles and access the class name without calling
tw
=>result.toString()
andresult.valueOf()
support template literal, strings, arrays, objects and other inline directives (incl
css
) as parametersRule Precedence Calculation
To have a predictable styling the styles must be ordered.
This order is represented by a precedence number. The lower values are inserted before higher values. Meaning higher precedence styles overwrite lower precedence styles.
Each rule has some traits that are put into a bit set which form the precedence:
Dark Mode: 1 bit
Flag for dark mode rules.
Layer: 3 bits
Screens: 1 bit
Flag for screen variants. They may not always have a
min-width
to be detected by Responsive below.Responsive: 5 bits
Based on extracted
min-width
value:At-Rules: 4 bits
Based on the count of special chars (
-:,
) within the at-rule.Pseudo and group variants: 17 bits
Ensures predictable order of pseudo classes.
Number of declarations (descending): 4 bits
Allows single declaration styles to overwrite styles from multi declaration styles.
Greatest precedence of properties: 4 bits
Ensure shorthand properties are inserted before longhand properties; eg longhand override shorthand
be lazy evaluated because it may never be used
Why lazy?
For one to prevent unnecessary style injection and to prevent problems when importing a component library that uses this API before invoking
setup
.Here are some examples using
tw.apply
to get a feeling for the API:Basic usage
Please note that the utility classes are always defined after the component styles which allows them to overrides certain styles.
twin.macro and styled-component compatibility eg generate one class
The would be possible as the returned function has
toString
andvalueOf
methods which inject the styles and return the class name:Or use this helper:
`css` can be used within `tw.apply`
Using within
css
– pendingtw.apply
can be used withcss
( (pending variable arguments, array support):Using template literal syntax (pending, but i'm working on it):
`twind/styled` would then be a small react wrapper around `tw.apply`
Using tailwind directives with `animation` from `twind/css`
A react button component
Discared Proposed Solutions
1. Nested
tw
(#73 (comment))tw
by itself behaves as it does now, untouchedtw
has a new behaviorOpen question @43081j: How to ensure that
generated-bg-blue
has a higher precedence thanbg-red
?2. Reverse Translation (#73 (comment))
Algorithm
Step 1 and 2 are possible. Step 3 may have some edge cases like what to do if the css is a partial match:
bg-opacity-5
partially overridesbg-red-500
. Both must be included in the output.Another edge case may be if the
css
helper is used. And i'm sure there a some i haven't identified yet.3. twind/compose
The following examples use template literals but well known
tw
arguments like strings, arrays, objects, and inline directives, would be supported.css
can be used withincompose
:Using within
css
– pending (Click to expand)compose
can be used withcss
( (pending variable arguments, array support):Using template literal syntax (pending, but i'm working on it):
twind/styled
would then be a small react wrapper around the basecompose
:Show more examples (click to expand)
Using tailwind directives with
animation
fromtwind/css
:Here is an example for an react button component:
4. Enhance
twind/css
css currently accepts an css object. We could extend it to accept strings which are directives:
css
would now be a translator from tailwind rules to css object.Show more examples (click to expand)
Using tailwind directives with
animation
fromtwind/css
:Here is an example for an react button component:
Summary
I hope have summarized all sides of the discussion and everybody sees theirs points reflected in the proposed solution.
Thank you for reading this whole thing ❤️
/cc @tw-in-js/contributors
The text was updated successfully, but these errors were encountered: