-
Notifications
You must be signed in to change notification settings - Fork 14
Supporting twin.macro
-like props?
#290
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
Comments
This look interesting... We might support I'll discuss this with @Mad-Kat who built the CSS JSX prop into next-yak |
That would be most excellent. |
Yes I think using |
@Mad-Kat What would be the equivalent of the following in <div css={[tw`flex gap-4`, isSomething ? tw`basis-8/12` : tw`w-full`]}> |
At the moment the dynamic styling of the CSS prop hasn't a very good support. One syntax that works is: <div
css={css`
${atoms('flex gap-4')}
${() =>
isSomething ?
css`${atoms('basis-8/12')}`
: css`${atoms('w-full')}`}
`}
/> Altough it's not very ergonomic, it should work. The other approach would be to use @jantimon do you have any idea on how to make the ergonomics for these cases (with atoms) better? |
Would something like this work? or is that not supported? <div
css={css`
${atoms('flex gap-4')}
${atoms(isSomething ? 'basis-8/12' : 'w-full')}
`}
/> |
You're right. This is even easier and should work perfectly fine as is. I will try to see if I can implement the shorthand syntax. Would that suffice for you? You can also use it like this at the moment: <div
css={css`${atoms('flex gap-4', isSomething ? 'basis-8/12' : 'w-full')}}
/> So once the shorthand is possible you could also write it like this: <div
css={atoms('flex gap-4', isSomething ? 'basis-8/12' : 'w-full')}
/> |
That looks great and should be sufficient. I'll see if other twin.macro use cases appear problematic. From a code layout viewpoint, be able to use an array is sometimes advantageous, so if you don't find it too odd, making <div css={atoms(['flex gap-4', isSomething ? 'basis-8/12' : 'w-full', isSomethingElse && 'absolute'])} /> which brings a question: would this work? i.e. does <div css={atoms('flex gap-4', isSomething && 'w-full')} /> Part of the reason I like twin.macro is because of how well it integrates with the jsx code, i.e. most of the time, the css/tw prop will be on the same line as the HTML tag or React component, not taking additional vertical space, making the code easy to read. I want to thank you for being open to this discussion. A Rust-based path forward for twin.macro (and eventually with Turbopack) has the potential for making a lot of twin.macro users happy. The babel plugin had unfortunately reached a hard-to-get-around impasse with everything moving quickly towards Rust and Turbopack, and the added complexity of RSC. |
We're always happy to help and shape the future of CSS-in-JS together. It's always valuable to have others try and report issues that we never experienced, because our focus was on our problem we had at the time. The first goal was always to replace Regarding your questions:
At the moment it only works with
I've tried to write it down here. Maybe I should add it to the installation. Because the CSS prop should be on all native elements, we need to "wrap & override" the JSX specification of React.
I tried to support these kind of operations with the CSS prop, but I don't know if it's the best way or if there are more intuitive ways. I would appreciate your input there to shape the API. type ComponentStyles<TProps> = (props: TProps) => {
className: string;
style?: {
[key: string]: string;
};
}; This is only to be able to pass the type CSSProp = {
/** This prop is transformed during compilation and doesn't exist at runtime. */
css?: ComponentStyles<Record<keyof any, never>>;
/** The css prop will return the name of the class and automatically merged with an existing class */
className?: string;
/** The css prop will return the style object and automatically merged with an existing style */
style?: {
[key: string]: string;
};
}; That one could be use to accept a // definition
const Test = ({ className, style }: {} & CSSProp) => {
return (
<div className={className} style={style}>
Test
</div>
);
};
// usage
<Test css={atoms("salmon")} /> Or you could also use it without a css prop: // use the return type of atoms function or manually annotate it with { className: string}
const Test = ({ myAtoms }: { myAtoms: { className: string } }) => {
return <div className={myAtoms.className}>Test</div>;
};
// usage. Note the empty parentheses to call the resulting function and create the className
<Test myAtoms={atoms("salmon")()} /> I know the whole part is not very fleshed out, but there should at least be a way for you to achieve the migration. I will sync with @jantimon to see if we come up with another idea to make this API better. |
@therealgilles Can you share some more examples how you use the Additionally I would love to know if you have more complicated cases to migrate and maybe you could share some of them? Just to think it through how our API should evolve and how easy or complicated it might be to migrate for you. |
I had already set Ah but I probably have a conflict with this import { css as cssImport } from '@emotion/react'
import { CSSInterpolation } from '@emotion/serialize'
import styledImport from '@emotion/styled'
import 'twin.macro'
declare module 'twin.macro' {
// The styled and css imports
const styled: typeof styledImport
const css: typeof cssImport
}
declare module 'react' {
// The tw and css props
interface DOMAttributes<T> {
tw?: string
css?: CSSInterpolation
}
interface HTMLAttributes<T> extends DOMAttributes<T> {
css?: Interpolation<Theme>
tw?: string
}
interface SVGProps<T> extends SVGProps<SVGSVGElement> {
css?: Interpolation<Theme>
tw?: string
}
} and I see I forgot to import |
Yes exactly. |
It does not seem possible to support both in the same file. I was hoping I could work around it by overriding the interfaces but I think it's a type, so it cannot be overridden. This makes the transition a bit trickier. Thinking about this more, maybe it wasn't such a good idea trying to pass processed atoms from component to component. Passing the original strings (or an array of strings) is probably best and it will make the transition easier too. I found an issue with <div css={[tw`bg-black`, tw`bg-[#0f0015]/40`]} /> would result in the latter having priority. But it appears it's not the case with |
Hmm am I not understanding how the css prop is supposed to work with |
Thank you for your answer. Supporting both in the same file will be very tricky, as both try to transpile some portion of the code. Would the way forward for you be to replace Yes keeping them string and only process at the end would be easier for us too. I might have found a solution for my issue with the css prop that is really inspired from emotion as their typings are really good. I will try to bake that into a PR. Regarding the class priorities. We don't do anything fancy with the provided strings. We just concat them and
Can you share more information? I would be happy to dive a bit in to see what goes wrong. |
I'm pretty sure everyone would want this. Let me try to explain. Here is what <div css={[tw`absolute top-0 left-0 h-full w-full bg-black`, tw`bg-[#0f0015]/40`]} /> gets transformed into this: <div data-tw="absolute top-0 left-0 h-full w-full bg-black | bg-[#0f0015]/40" class="tw-oqf9q5"></div> The .tw-oqf9q5 {
position: absolute;
left: 0px;
top: 0px;
height: 100%;
width: 100%;
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));
background-color: rgb(15 0 21 / 0.4);
} It's easy to see that the latter background color tag will take priority over the former one because it's placed after. BUT this only works because they are grouped into the same class. Does this make sense? This is the only way that Tailwind makes sense with CSS-in-JS. At least in my opinion. Maybe I can try tailwind-merge but that's really not the same. Ultimately what I want is a single class. I have no interest in Tailwind classes being included one by one. |
Is it not possible to run Tailwind to transform the tags into css or styles object and then process that the same way |
Thanks @therealgilles for sharing all these insights Here is my personal opinion 1. A dedicated
|
Thank you for offering these ideas. About combining Tailwind and CSS modules, it depends what you mean by that. I don't have separate CSS modules in my project, except for global styles (including tailwind style reset i.e. preflight, which is unavoidable). I prefer getting computed classes in the resulting HTML. It feels more efficient/compact and I would imagine results in less CSS overall. Maybe that's just a personal preference. Using classes makes the usage of
On the other hand, it does add computation to generate the classes from the tags. |
Yes I think that should be possible. In general I agree very much with your statement.
If you would use
The tailwind compiler is smart and tries to remove all classes from tailwind that aren't needed in your codebase and that reduces the whole size by a huge margin. Additionally the idea of "utility CSS" is to avoid specialized classes for certain aspects and should result in less CSS LOC overall in bigger projects.
You're right. If you use our So this code: styled.div`
${atoms("bg-black mb-4")}
`; Would result in this HTML <div class="bg-black mb-4" /> |
I understand your point of view. Philosophical opinions are many and diverse when it comes to atom-based CSS and CSS-in-JS, such as:
and they keep evolving. I can also imagine why the authors of Tailwind would want to see their utility classes in the resulting HTML instead of CSS module classes. I don't work on any large projects with Tailwind. Mine are mostly small to medium. I mostly see Tailwind as a way to make writing CSS both easier and better. From a DX perspective, it also makes finding the corresponding code super easy as I can just copy a few of the utility classes (present in With your proposal, I don't see a solution that does not involve tailwind-merge, apart from managing the Tailwind class priorities myself, or never passing "dynamic"* atoms between components (some people may see that as an anti-pattern). Having to use tailwind-merge at runtime feels like a big disadvantage to me. Without it, the runtime JS just has to switch between classes or different CSS variable values based on props, way more efficient. Here is an example of how I use dynamic* atoms between components: I'm not on X but I read that many people disagree with Adam Wathan's position on In any case, it is obviously up to you to decide what is best for (*) By "dynamic", I mean conditional. |
I just gave tailwind-merge a go and it's not doing what I expect. This: <div className={twMerge('bg-black', 'bg-opacity-50')} /> gets merged as: <div className={'bg-opacity-50'} /> which only sets a CSS variable, so in effect does nothing, whereas twin.macro generates: .tw-oqf9q5 {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));
--tw-bg-opacity: 0.5;
} which generates a 50% opacity black overlay. Of course I could replace |
Correct - the tailwind-merge behavior with I'm curious though - if you switched the order in the twin.macro case to .tw-oqf9q5 {
--tw-bg-opacity: 0.5;
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));
} Off-topic: in next-yak you could just write But you are right runtime class merging (tailwind-merge) is slower than build time merging.. I am just not sure that next-yak users might expect tailwind classes to be compiled especially given that tailwind recommends utility classes in HTML instead CSS merging can also cause issues with order (especially in the current situation where CSS ordering is broken in next.js on production vercel/next.js#72846). In the end next-yak is a CSS-in-JS solution and we want to make a good tradeoff between supporting tailwind for great DX while avoiding too much complexity by becoming a tailwind compiler engine |
That's correct but that's what I would expect i.e. the latter utility class takes precedence if it sets the same CSS properties.
Ideally users would be able to choose. Or
Sure but the non-atom part of I took a look at the nextjs bug report and I'm actually not sure this could happen with twin.macro or what I propose. Each component only gets a single class, therefore only the order of global CSS vs local CSS matters, and they would hopefully be in separate stylesheets. Anyway, I'm just here sharing my preference – mostly based on my experience with twin.macro. I wish I could just keep using it and that it would work with Rust-based tooling like Turbopack. Right now, I have two different versions of tailwind installed due to twin.macro referencing internal functions of v3 that don't exist in v4 anymore. Obviously not a maintainable workflow going forward, so looking at alternatives. Do what you think is best for next-yak :) |
Any chance to support twin.macro-like component props like:
I'm trying out the
css/atoms
combination but it's so less efficient code-wise withprettier
putting everything on multiple lines:The text was updated successfully, but these errors were encountered: