tw-in-js / twind Goto Github PK
View Code? Open in Web Editor NEWThe smallest, fastest, most feature complete Tailwind-in-JS solution in existence.
Home Page: https://twind.style
License: MIT License
The smallest, fastest, most feature complete Tailwind-in-JS solution in existence.
Home Page: https://twind.style
License: MIT License
Please see this minimum example
<section class="grid grid-cols-3 gap-2 place-items-center h-screen w-screen">
<div class="bg-pink-500 text-white justify-self-start">1</div>
<div class="bg-pink-500 text-white">2</div>
<div class="bg-pink-500 text-white">3</div>
<div class="bg-pink-500 text-white">4</div>
<div class="bg-pink-500 text-white">5</div>
<div class="bg-pink-500 text-white">6</div>
</section>
Correct Result
But in twind
, it's not working
Official Docs here
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:
<button class="label:PurpleButton py-4 px-8 />
And
compose`
label:PurpleButton
py-4 px-8
rounded-lg
bg-purple-600`
// -> .PurpleButton-XXXX
The separator could be either a :
(label:x) to be like emotion, or a -
(label-x) to be more tailwind like.
Based on #73 (comment) and #73 (comment)
Pseudo elements like ::before
and ::after
are denoted with a double colon. These can be currently only supported defining them as additional variants (setup({variants}) and core variants):
setup({
variants: {
before: '&::before',
after: '&::after',
}
})
tw`before:block`
To support these within Twind I see two options:
{
after: '&::after',
before: '&::before',
'first-letter': '&::first-letter',
'first-line': '&::first-line',
selection: '&::selection',
}
Usage:
tw`before:block`
import { pseudoElements } from '@twind/pseudo-elements'
setup({
variants: pseudoElements
})
tw`before:block`
tw`before::block`
Which options do you prefer? Or do you see another way?
/cc @tw-in-js/contributors
Hi, thanks a lot for this project!
I am trying to figure out how I can configure custom font face which is self-hosted. In Tailwind I can do it in CSS: https://tailwindcss.com/docs/adding-base-styles#font-face-rules. How can I do same but in Twind?
Thanks
Hello,
As far as I see there is no support to customize any of the grid properties (e.g. gridTemplateColumns
). I tried to search in issues and discussions but didn't find any information. Is there any plans to add such support ?
Thanks
Please see this minimum example
In this Tailwind Play I created, the color theme can be extended down to three-level like this
module.exports = {
theme: {
extend: {
colors: {
v2: {
red: {
500: '#DC2626',
},
},
},
},
},
}
<div class="text-v2-red-500">Three level custom color</div>
Result:
However, in twind, it's not working, and typing yells also
Result:
Hey there, this is an interesting tool. I love that you don't have to mess around with the build step to use it. However, the tailwind intellisense doesn't work when using this library which is killer. I noticed the VS code extension says it will activate if "tailwind css is installed and a tailwind config exists". But obviously there's no reason to install tailwind if using twind. Any thoughts here or something I'm missing? Thanks!
Hi all, fantastic work on twind
!
I was looking into integrating this into an experiment of mine that brings full async SSR but noticed that twind
's SSR docs appear to assume that only one concurrent render operation can be in effect at once. Are there any tricks I'm missing that would allow me to potentially:
The potential for interleaving and out-of-order completion has me a bit worried.
Hello, thanks for making Twind, it's really great!
I think I came across one bug in a complicated situation, if css
, tw
, and apply
are used like this:
tw(
css({
h1: apply`bg-accent`
})
)
(which I think is how they're supposed to be used if you want to get a className
to use)
After calling that bg-accent
no longer takes effect in normal tw
usage.
Reproduction: https://codesandbox.io/s/twind-bug-css-consumes-theme-value-jnnrm?file=/src/App.js:232-283
Thank you!
I haven’t been able to get the examples in the twind/css
docs to work as the content
property doesn’t seem to be processed by the CSS directive. Other properties (background
in the example) are passed through and displayed when ::before
or ::after
content is set manually.
import { css } from "https://cdn.skypack.dev/twind/css";
const styles = css({
"&::before": { background: "red", content: "🙁" },
"&::after": { background: "green", content: "😊" }
});
document.body.innerHTML = `
<style>
/* override */
.${styles}::after { content: "👋" }
</style>
<p class="${styles}">Hi!</p>
`;
Hi,this is an amazing tool, thank you for you work. I'll try to use twind inside the new Preact WMR ( https://github.com/preactjs/wmr ) but with the only npm install twind and tailwindcss and the following use:
import { useState } from 'preact/hooks';
import { tw } from 'twind'
export default function Home() {
return (
<>
<p>Hi!</p>
<h1 className={tw`font-bold text(center 5xl white sm:gray-800 md:pink-700)`}>This is Twind!</h1>
</> );
}
doesn't work, i have a white page and no error as output of npm run start. Is it possible to use with Preact WMR?
Thank you.
for lazy Node devs this would be ideal:
npm install twind
import { tw, getStyleTag } from 'twind'
let html = `
<div class="${tw`text-xl`} block px-20 text-green-100">
can optionally use the tw function or vanilla Tailwind to sprinkle classes
</div>
`
let styleTag = getStyleTag(html)
//profit
here the tw
function needs no dom or sheet; operating directly on the string provided. The getStyleTag
function can do its thing to get only the styles used whether vanilla classes or those created with said tw function; and without using a shim.
Those other features obviously useful for more advanced use cases but here in the simplest possible server side use case it could become obvious how to get what we need without further ritual or mental energy/documentation necessary to understand other concepts.
I'm adding twind to an existing React project with its own CSS (via emotion). I noticed that twind styles are well isolated to the components that use them, but once the component unmounts, some of the base styles (resets, I think) linger on the page.
Is there a recommended approach to cleanly removing these when the component is unmounted?
token
: things that can be passed to tw
(string, array, object, falsey values)rules
: rule
, group
rule
: variants
, directive
directive
: negate
, plugin
, params
negate
: should a theme value be negatedplugin
: utility
, component
params
: parameters for plugins; usually the key in the themePhases (details below)
Feature evaluation based on:
common types used below
type Falsy = '' | 0 | -0 | false | null | undefined
interface TokenGrouping extends Record<string, Token> {}
type Token = string | TokenGrouping | Token[] | Falsy
interface Context {
/** allow composition */
tw: TW
/** access to theme values */
theme(section: string): ThemeSection
theme(section: string, key: string | string[]): string | undefined
theme(section: string, key: string | string[], defaultValue: string): string
/** create unique identifier (group, custom properties) */
tag(key: string): string
}
converts rules into CSS class names
interface TW {
(strings: string[], ...replacements: Token[]): string
(...rules: Token[]): string
}
customize
tw
interface Setup {
preflight?: boolean | Preflight
mode?: Mode
theme?: Partial<Theme> | ((defaultTheme: ThemeResolver) => Partial<Theme>)
hash?: boolean | Hasher
plugins?: Record<string, string | CSSRules | Plugin>
injector?: Injector
prefix?: boolean | Prefix
}
Add global styles before first rule is injected
default: tailwind preflight
false
interface Preflight {
(preflight: CSSRules, context: Context): CSSRules | undefined
}
customize behaviour
default: warn
interface Mode {
/**
* Notify error (missing plugin, duplicate directives? )
*
* Why id?
* - can generate an url with more info
* - reduce bundle size by omitting large error messages
*/
report(info: {id: string}, context: Context): void
/** Called for unknown theme values */
unknown(
section: string,
key: string,
optional: boolean,
context: Context,
): string | undefined
}
default:
false
default: tailwind
allows to define/override plugins
default: core plugins
Plugins are searched for by name using the longest prefix before a dash ("-"'
). The name and the remaining parts (splitted by a dash) are provided as first argument to the plugin function. For example if the directive is bg-gradient-to-t
the following order applies:
Plugin | Params |
---|---|
bg-gradient-to-t |
[] |
bg-gradient-to |
["t"] |
bg-gradient |
["to", "t"] |
bg |
["gradient", "to", "t"] |
Arguments
Return Types
&
for nested selectors)group
and lead
return a marker class name that should be used as ismode.unknown()
then mode.report()
interface Plugin {
/**
* Creates CSSRules based on `parts`
*/
(params: string[], context: Context, id: string): CSSRules | string | Falsy
}
Examples
const plugins = {
block: { display: 'block' },
placeholder: (params, { theme }) => {
const value = theme('placeholderColor', params)
return value && {
'&::placholder': {
color: value
},
}
},
rotate: (params, { theme }) => {
const value = theme('rotate', tail(params))
return value && {
'--tw-rotate': value,
transform: [
`rotate(${value})`,
`translateX(var(--tw-translate-x),0)) translateY(var(--tw-translate-y),0) rotate(var(--tw-rotate,0))`
],
}
},
/** use tag to create marker class */
group: (params, { tag }) => tag('group'),
/** use tw to compose */
card: (params, { tw }) => tw`max-w-md mx-auto bg-white`
}
insert CSS rules into runtime
default: based on enviroment
interface Injector {
insert(rule: string, index: number): void
delete(index: number): void
}
CSS prefixing
default: based on tiny-css-prefixer
false
interface Prefixer {
(property: string, value: string): string
}
parse tokens in rules
interface Parse {
(strings: string[], replacements: Token[]): Rule[]
(...rules: Token[]): Rule[]
}
type Token = string | Falsy | Record<string, Token> | Token[]
interface Rule {
/** ["sm", "dark", "hover"] */
variants: string[]
/** "text-sm", "-rotate-45" */
directive: string
}
character = letter | digit;
identifier = character, { character };
whitespace = ' ' | '\t' | [ '\r' ], '\n';
(*
- "h-full bg-purple-500 rotate-3 scale-95"
- "w(1/2 sm:1/3 lg:1/6) p-2"
- "divide(y-2 blue-500 opacity(75 md:50))"
- "ring(& ping-700 offset(4 ping-200))"
- "rotate(-3 hover:6 md:(3 hover:-6))"
*)
rules =
{ whitespace },
group | rule,
{ { whitespace }-, group | rule },
{ whitespace } ;
group = variantGroup | directiveGroup;
variantGroup =
{ variant, ":" }-,
'(',
{ rules }-,
')';
directiveGroup =
{ variant, ":" },
plugin,
'(',
{ "&" | rules }-,
')';
rule = { variant, ":" }, directive;
variant = identifier;
directive = [ "-" ], plugin, { "-", params };
plugin = identifier, { "-", identifier };
params = param, { "-", param };
param = identifier | "_" | "/" | ".";
translate rules into CSSRules using plugins
Why CSSRules?
container
, placeholder
, divide
, prose
, ...interface Translate {
(rule: Rule, context: Context): CSSRules
}
import * as CSS from 'csstype'
interface CSSProperties extends CSS.PropertiesFallback, CSS.PropertiesHyphenFallback {}
interface CSSRules extends CSSProperties {
/** @media, @supports, @keyframes, ... */
[`@${string}`]: CSSRules
/** pseudo class
* maybe -> could be implement using '&:'
* watch out for ':root' -> that could use '*' instead
*/
[`:${string}`]: CSSRules
/** maybe allow '&' everywhere */
[`${string}&${string}`]: CSSRules
/** global defaults */
'*': CSSProperties
}
insert CSSRules into the enviroment returning a string with class names
interface Inject {
/** we need the parsed rule for class name generation and precedence calculation */
(css: CSSRules, rule: Rule | undefined, context: Context): string
}
monorepo
name: why not go with ocean/beamwind
group-*
variant works for every pseudo class
This allows to use group-focus-with
or group-active
automatically infers negated values - they do not need to be in the theme config
bg-gradient-to-*
is built-in, no need to configure these
text-underline
, text-no-underline
, text-line-through
, text-uppercase
, text-lowercase
and text-capitalize
: this allows grouping of text directives like text(lg red-500 capitalize underline)
font-italic
and font-no-italic
: this allows grouping of font directives like font(sans italic bold)
border
and divide
allow to combine positions (t
op, r
righ, l
eft, b
ottom)
tr
- top
& right
brl
- bottom
, right
and left
Note
x
andy
can not be combined.
rotate
, scale
, skew
and translate
provide a fallback for IE 11
transform rotate-45
works but when usingtransform rotate-45 scale-150
only one of both is applied.
Some new tailwind features use CSS Variables (Custom Properties) and are therefore not compatible with IE 11.
tw-in-js includes fallbacks for the following directives which mimic Tailwind v1 behavior:
Color Opacity
Reversing Children Order
rotate
, scale
, skew
and translate
can only be used alone
rotate-45
works but when usingrotate-45 scale-150
only one of both is applied. In that case you must usetransform
:transform rotate-45 scale-150
Some directive only work with CSS Variables and are not supported in IE 11:
It would be very helpful to have a content
plugin (@twind/content) which allows to define CSS content property.
Why not in core? There is not a official Tailwindcss
content
specification. By not adding it to core we prevent future incompatibilities.
Prior Work
Some features described below depend on pseudo elements support (#103).
Usage as Directive
❗ Values must be adhere to CSS content property syntax.
tw`${content('"✅"')}`
// => .tw-xxxx { content: "✅" }
tw`before::${content('"✅"')}`
// => .tw-xxxx::before { content: "✅" }
tw`before::${content('attr(data-content)')}`
// => .tw-xxxx::before { content: attr(data-content) }
tw`after::${content('" (" attr(href) " )"')}`
// => .tw-xxxx::after { content: " (" attr(href) " )" }
tw`${content({ before: '"💡"', after: '"✅"' })}`
// => .tw-xxxx::before { content: "💡" }
// => .tw-xxxx::after { content: "✅" }
tw`${content({ before: 'attr(data-before)', after: 'attr(data-after)' })}`
// => .tw-xxxx::before { content: attr(data-before) }
// => .tw-xxxx::after { content: attr(data-after) }
Usage as Plugin
I see the following usage patterns which should be tried in order:
content-<theme>
with { theme: { content: { key: value } } }
tw`before::content-check` // with { theme: { content: { check: '"✅"' } } }
// => .before\:\:content-check::before { content: "✅"; }
content-(known value)
List of known values:
open-quote
close-quote
no-open-quote
no-close-quote
normal
none
inherit
initial
unset
tw`before::content-open-quote`
// => .before\:\:content-open-quote::before { content: open-quote; }
content[-(attr|data|counter|var)-value]
tw`content`
// => .content { content: attr(data-content); }
tw`before::content`
// => .before\:\:content::before { content: attr(data-content); }
tw`before::content-data-before`
// => .before\:\:content-data-before::before { content: attr(data-before); }
tw`before::content-attr-data-before`
// => .before\:\:content-attr-data-before::before { content: attr(data-before); }
tw`before::content-counter-chapter_counter`
// => .before\:\:content-attr-data-before::before { content: counter(chapter_counter); }
tw`before::content-var-before`
// => .before\:\:content-var-before-before::before { content: var(--before); }
content-value
using JSON.stringify
tw`before::content-✅`
// => .before\:\:content-✅::before { content: "✅"; }
Do you have any comments? Did I miss something?
/cc @tw-in-js/contributors
Thank you all for the feedback, questions, ideas and suggestions. I have edited the proposed solution o reflect the discussion.
Proposed Draft 2020-13-01 04:50 GMT
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.
As a component author I want to re-use tailwind directive styles for defining my component and allow users of the component to override styles using tailwind directive. Additionally I want to be able to extend a component and override or add some styles using tailwind rules.
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.
const Button = ({ className, children}) => {
return <button className={tw`inline-block 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>
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. The bg
prefix and its plugin handle several css properties where background-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:
const Button = twind.button`
text(base blue-600)
rounded-sm
border(& solid 2 blue-600)
m-4 py-1 px-4
`
// Create a child component overriding some colors
const PurpleButton = twind(Button)`
override:(text-purple-600 border-purple-600)
`
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 the PurpleButton
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.
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.
.btn-indigo {
@apply py-2 px-4 bg-indigo-500 text-white font-semibold rounded-lg shadow-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-opacity-75;
}
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:
const hoverStyles = css`
&:hover {
border-color: black;
${tw`text-black`}
}
`
const Input = ({ hasHover }) => (
<input css={[tw`border`, hasHover && hoverStyles]} />
)
The
tw
function fromtwin.macro
acts like the@apply
helper from tailwindcss.
Lets summarize both composition approaches:
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)
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 togetherconst btn = tw.apply`inline-block bg-gray-500 text-base`
// => generates on css class with all declarations of the above rules when used
const btnBlick = tw.apply`${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
That 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 directive
allow to inject the styles and access the class name without calling tw
=> result.toString()
and result.valueOf()
support template literal, strings, arrays, objects and other inline directives (incl css
) as parameters
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:
bits | trait |
---|---|
1 | dark mode |
2 | layer: base = 0, components = 1, utilities = 2 , css = 3 |
1 | screens: is this a responsive variation of a rule |
5 | responsive based on min-width |
4 | at-rules |
17 | pseudo and group variants |
4 | number of declarations (descending) |
4 | greatest precedence of properties |
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
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:
Please 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 { ... }
The would be possible as the returned function has toString
and valueOf
methods which inject the styles and return the class name:
<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
const btn = tw.apply`
py-2 px-4
${css({
borderColor: 'black',
})}
`
css
– pendingtw.apply
can be used with css
( (pending variable arguments, array support):
const prose = css(
tw.apply`text-gray-700 dark:text-gray-300`,
{
p: tw.apply`my-5`,
h1: tw.apply`text-black dark:text-white`,
},
{
h1: {
fontWeight: '800',
fontSize: '2.25em',
marginTop: '0',
marginBottom: '0.8888889em',
lineHeight: '1.1111111',
}
}
)
Using template literal syntax (pending, but i'm working on it):
const prose = css`
${tw.apply`text-gray-700 dark:text-gray-300`)
p { ${tw.apply('my-5')} }
h1 {
${tw.apply`text-black dark:text-white`}
font-weight: 800;
font-size: 2.25em;
margin-top: 0;
margin-bottom: 0.8888889em;
line-height: 1.1111111;
}
`
const Button = twind.button`
text(base blue-600)
rounded-sm
border(& solid 2 blue-600)
m-4 py-1 px-4
`
const PurpleButton = twind(Button)`
text-purple-600 border-purple-600
`
const motion = animation('.6s ease-in-out infinite', {
'0%': tw.apply`scale-100`,
'50%': tw.apply`scale-125 rotate-45`,
'100%': tw.apply`scale-100 rotate-0`,
})
import { tw } from 'twind'
const variantMap = {
success: "green",
primary: "blue",
warning: "yellow",
info: "gray",
danger: "red"
}
const sizeMap = {
sm: tw.apply`text-xs py(2 md:1) px-2`,
md: tw.apply`text-sm py(3 md:2) px-2`,
lg: tw.apply`text-lg py-2 px-4`,
xl: tw.apply`text-xl py-3 px-6`
}
const baseStyles = tw.apply`
w(full md:auto)
text(sm white uppercase)
px-4
border-none
transition-colors
duration-300
`
function Button({ size = 'md', variant = "primary", round = false, disabled = false, className, children }) {
// Collect all styles into one class
const instanceStyles = tw.apply`
${baseStyles}
bg-${variantMap[variant]}(600 700(hover:& focus:&)))
${sizeMap[size]}
rounded-${round ? "full" : "lg"}
${disabled && "bg-gray-400 text-gray-100 cursor-not-allowed"}
`
// Allow passed classNames to override instance styles
return <button className={tw(instanceStyles, className)}>{children}</button>
}
render(<Button variant="info" className="text-lg rounded-md">Click me</Button>)
tw
(#73 (comment))tw
by itself behaves as it does now, untouchedtw
has a new behaviortw`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 .tw-generated-bg-blue{} is appended
// result is bg-red tw-generated-bg-blue
Open question @43081j: How to ensure that generated-bg-blue
has a higher precedence than bg-red
?
Enhance
tw
to detect directives that override previous ones and omit those from the result class names string.
const btn = tw`py-2 px-4 font-semibold`
// => py-2 px-4 font-semibold
tw`${btn} py-4 px-8`
// => font-semibold py-4 px-8
tw`py-4 ${btn} px-8`
// => py-2 font-semibold px-8
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-red-500 {
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
}
.bg-opacity-5 {
--tw-bg-opacity: 0.05;
}
bg-opacity-5
partially overrides bg-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.
Introduce
compose
as a new function which would extract the styles of the provided directives and returns an inline directive with an css style object containing all deep merged rules which can be used withtw
. The generated styles would have a lower precedence than the utility classes which would allow to use tailwind directives to override styles.
The following examples use template literals but well known tw
arguments like strings, arrays, objects, and inline directives, would be supported.
import { compose } from 'twind/compose'
const btn = compose`
py-2 px-4
font-semibold
rounded-lg shadow-md
focus:(outline-none ring(2 indigo-400 opacity-75))
`
// Result: () => ({ paddingTop: '0.5rem', paddingBottom: '0.5rem', paddingLeft: '1rem', paddingRight: '1rem', fontWeight: '600', ... })
// CSS:
// .tw-XXXX { padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 1rem; padding-right: 1rem; font-weight: 600; ...}
const btnLarge = compose`${btn} py-4 px-8`
// Result: () => ({ paddingTop: '1rem', paddingBottom: '1rem', paddingLeft: '2rem', paddingRight: '2rem', fontWeight: '600', ... })
// CSS:
// .tw-YYYY { padding-top: 1rem; padding-bottom: 1rem; padding-left: 2rem; padding-right: 2rem; font-weight: 600; ... }
tw`${btnLarge} rounded-md`
// => .tw-btn .tw-btn-large .rounded-md
css
can be used within compose
:
const btn = compose`
py-2 px-4
${css({
borderColor: 'black',
})}
`
css
– pending (Click to expand) compose
can be used with css
( (pending variable arguments, array support):
const prose = css(
compose`text-gray-700 dark:text-gray-300`,
{
p: compose`my-5`,
h1: compose`text-black dark:text-white`,
},
{
h1: {
fontWeight: '800',
fontSize: '2.25em',
marginTop: '0',
marginBottom: '0.8888889em',
lineHeight: '1.1111111',
}
}
)
Using template literal syntax (pending, but i'm working on it):
const prose = css`
${compose`text-gray-700 dark:text-gray-300`)
p { ${compose('my-5')} }
h1 {
${compose`text-black dark:text-white`}
font-weight: 800;
font-size: 2.25em;
margin-top: 0;
margin-bottom: 0.8888889em;
line-height: 1.1111111;
}
`
twind/styled
would then be a small react wrapper around the base compose
:
const Button = twind.button`
text(base blue-600)
rounded-sm
border(& solid 2 blue-600)
m-4 py-1 px-4
`
const PurpleButton = twind(Button)`
text-purple-600 border-purple-600
`
Using tailwind directives with animation
from twind/css
:
const motion = animation('.6s ease-in-out infinite', {
'0%': compose`scale-100`,
'50%': compose`scale-125 rotate-45`,
'100%': compose`scale-100 rotate-0`,
})
Here is an example for an react button component:
import { tw } from 'twind'
import { compose } from 'twind/compose'
const variantMap = {
success: "green",
primary: "blue",
warning: "yellow",
info: "gray",
danger: "red"
}
const sizeMap = {
sm: compose`text-xs py(2 md:1) px-2`,
md: compose`text-sm py(3 md:2) px-2`,
lg: compose`text-lg py-2 px-4`,
xl: compose`text-xl py-3 px-6`
}
const baseStyles = compose`
w(full md:auto)
text(sm white uppercase)
px-4
border-none
transition-colors
duration-300
`
function Button({ size = 'md', variant = "primary", round = false, disabled = false, className, children }) {
const instanceStyles = compose`
${baseStyles}
bg-${variantMap[variant]}(600 700(hover:& focus:&)))
${sizeMap[size]}
rounded-${round ? "full" : "lg"}
${disabled && "bg-gray-400 text-gray-100 cursor-not-allowed"}
`
// Allow passed classNames to override instance styles
return <button className={tw(instanceStyles, className)}>{children}</button>
}
render(<Button variant="info" className="text-lg rounded-md">Click me</Button>)
twind/css
Extend
twind/css
to extract the styles of the provided directives and return an inline directive with an css style object containing all deep merged rules which can be used withtw
. The generated styles would have a lower precedence than the utility classes which would allow to use tailwind directives to override styles.
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.
Please note that the template literal syntax may come with issues in editors and prettier as it may be mistaken for real css. If anyone has a solution please comment below.
const btn = css('py-2 px-4 font-semibold')
// => { padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 1rem; padding-right: 1rem; font-weight: 600; }
tw`${btn} py-4 px-8`
// => tw-xxx py-4 px-8
const largeBtn = css`${btn} py-4 px-8`
// => { padding-top: 1rem; padding-bottom: 1rem; padding-left: 2rem; padding-right: 2rem; font-weight: 600; }
tw`${largeBtn} font-bold`
// => tw-yyyy font-bold
Using tailwind directives with animation
from twind/css
:
import { css, animation } from 'twind/css'
const motion = animation('.6s ease-in-out infinite', {
'0%': css('scale-100'),
'50%': css('scale-125 rotate-45'),
'100%': css('scale-100 rotate-0'),
})
Here is an example for an react button component:
import { tw } from 'twind'
import { css } from 'twind/css'
const variantMap = {
success: "green",
primary: "blue",
warning: "yellow",
info: "gray",
danger: "red"
}
const sizeMap = {
sm: css('text-xs py(2 md:1) px-2'),
md: css('text-sm py(3 md:2) px-2'),
lg: css('text-lg py-2 px-4'),
xl: css('text-xl py-3 px-6')
}
const baseStyles = css(`
w(full md:auto)
text(sm white uppercase)
px-4
border-none
transition-colors
duration-300
`)
function Button({ size = 'md', variant = "primary", round = false, disabled = false, className, children }) {
const instanceStyles = css(`
${baseStyles}
bg-${variantMap[variant]}(600 700(hover:& focus:&)))
${sizeMap[size]}
rounded-${round ? "full" : "lg"}
${disabled && "bg-gray-400 text-gray-100 cursor-not-allowed"}
`)
// Allow passed classNames to override instance styles
return <button className={tw(instanceStyles, className)}>{children}</button>
}
render(<Button variant="info" className="text-lg rounded-md">Click me</Button>)
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
Currently we interpret objects as nested rules (see docs/usage):
tw({
sm: 'w-1/2',
md: 'w-1/3',
})
// => sm:w-1/2 md:w-1/3
tw`sm:${{ underline: true }}`
// => sm:underline
I wonder if there is really a use case for this kind of rule definitions?
The above examples could be written using the preferred (performance) template literal syntax:
tw`sm:w-1/2 md:w-1/3`
tw`w(sm:1/2 md:1/3)`
// => sm:w-1/2 md:w-1/3
tw`sm:${true && 'underline'}`
tw`sm:${[true && 'underline']}`
// => sm:underline
To have custom css you currently need to use inline plugins (a function returning a css object):
tw(() => ({
'&::before': { content: '🙁' }
'&::after': { content: '😊' }
}))
// => sm:hover:tw-xxxxx
I propose that we drop the nested rules object in favor of CSS objects:
tw`
sm:hover:${{
'&::before': { content: '🙁' }
'&::after': { content: '😊' }
}}
// => sm:hover:tw-xxxxx
This would make interpolations values the same as the possible return values of plugins and unify the api.
The function syntax (tw(({ theme, tag }) => ({ ... }) )
) would still be supported to access the theme and tag functions.
Additionally this would allow to use tw
as css
function like in most other css-in-js libraries:
tw({
'&::before': { content: '🙁' }
'&::after': { content: '😊' }
})
// => tw-xxxxx
@bebraw @lukejacksonn What do you think? This should be settled before the initial public release (coming next week).
Variants for css objects should be supported as well:
tw`sm:${{
'&::before': { content: '🙁' }
'&::after': { content: '😊' }
}}`
// => sm:tw-xxxxx
We could introduce a css function/package for style re-use which would allow single use as well:
import { css } from 'twind/css'
const smiley = css({
'&::before': { content: '🙁' }
'&::after': { content: '😊' }
})
// smiley is an object or function (inline plugin) to lazily inject the styles on first use
smiley.valueOf()
smiley.toString()
`${smiley}`
// => tw-xxxxx
document.body.className = smiley
document.body.classList.add(smiley)
// Or use as interpolation value
tw`sm:${smiley}`
// => sm:tw-xxxxx
So one issue I notice with the way the API works, is that you have to manually ensure tw is only called after setup. Integrating twind in my Next.js project and having followed the examples, even then, I'm still getting some "late setup call" errors, which I think is a sign that this constraint can't be reliably followed
Suggestion: when needing a config override, the user should create their own tw
file which includes the setup
call. Setup returns the tw
function, which is then imported throughout the app for styling. Logically, this ensures that setup
can't be called after tw
. To clarify:
// tw.ts
import { setup } from "twind"
export const tw = setup({
theme: { colors: { palevioletred: "palevioletred" } },
})
// Button.tsx
import { tw } from "./tw"
export const Button = () => (
<button type="button" className={tw`p-3 rounded bg-blue-500 text-white`}>
hi
</button>
)
With the current API, it's true that one could also reexport tw
from the tw.ts
file. My suggestion is to make this API change to possibly enforce using the returned tw
function, and/or document that usage
It would be useful if we could target a constructed stylesheet which we can then use via shadowRoot.adoptedStyleSheets = [twindSheet]
.
This would allow us to create the stylesheet once and re-use it across multiple web components without having the side effect of polluting the root document's styles.
I've had a read through the source and it seems the current "sheet" implementation is using CSSOM to add rules to a stylesheet already. However, its default target is a style tag appended to the DOM.
So maybe its as simple as having a different target in the existing cssom sheet? A reference to a sheet rather than one we have in DOM.
Though then the question is, how do we get hold of that per web component?
class SomeElement extends HTMLElement {
// ...
connectedCallback() {
// how do we get hold of this sheet?
this.shadowRoot.adoptedStyleSheets = [REF_TO_TWIND_SHEET];
}
// Assume this just ends up appending to this.shadowRoot
render() {
return `<div class="${tw`bg-black`}">Foo</div>`;
}
}
As you can see, we'd probably need to create the stylesheet ourselves and tell twind to append to it. Like during setup()
of twind.
Though that leaves us with a choice to make too:
Could do with some christmas coding so would be happy to have a go at this once i get my head around it
This issue was originally identified in this discussion: #44 (comment)
I've included a minimal example which appears to confirm @sastan's suspicion that this issue is specific to template literal syntax. It appears to only happen when referencing outside variables.
Codesandbox Example: https://codesandbox.io/s/twind-template-literal-issue-t3zwf
Tailwindcss Plugin Documentation
Supporting plain tailwindcss plugins would allow users to re-use existing plugins and prevent introducing a new plugin API.
addUtilities()
, for registering new utility stylesplugin(function({ addUtilities }) {
const newUtilities = {
'.skew-10deg': {
transform: 'skewY(-10deg)',
},
'.skew-15deg': {
transform: 'skewY(-15deg)',
},
}
addUtilities(newUtilities)
})
We can convert the CSS-in-JS object using twind/css
. For each top-level key extract the plugin name and register it's definiton as a twind plugin.
addUtilities()
accepts several options. We should discuss if support for these is required for an initial release.
addUtilities(newUtilities, {
respectPrefix: false,
respectImportant: false,
variants: ['responsive', 'hover']
})
To be fully compatible we should support prefix and important configuration settings.
addComponents()
, for registering new component stylesWorks just like addUtilities()
but adds the generated classes into the component layer. We could increase the presedence for these to simulate a component layer.
addBase()
, for registering new base stylesThis should add the generated classes to the preflight. They should not be hashed or prefixed.
addVariant()
, for registering custom variantsAllows you to register your own custom variants that can be used just like the built-in hover, focus, active, etc. variants.
plugin(function({ addVariant, e }) {
addVariant('disabled', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.${e(`disabled${separator}${className}`)}:disabled`
})
})
})
e()
, for escaping strings meant to be used in class namesAlready implemented in util.ts
as escape
.
prefix()
, for manually applying the user's configured prefix to parts of a selectorThe prefix function will prefix all classes in a selector and ignore non-classes, so it's totally safe to pass complex selectors like this one
prefix('.btn-blue .w-1\/4 > h1.text-xl + a .bar')
// => '.tw-btn-blue .tw-w-1\/4 > h1.tw-text-xl + a .tw-bar'
theme()
, for looking up values in the user's theme configurationAllow to access the theme. Already implemented within the theme resolver.
variants()
, for looking up values in the user's variants configurationThis may be no-op as twind supports all variants for every directive.
config()
, for looking up values in the user's Tailwind configurationProvides access to the default configuration.
postcss
, for doing low-level manipulation with PostCSS directlyThis may never be available as twind is not using postcss. On accessing the property an error should be thrown.
setup
must support an array of functions:
setup({
plugins: [
plugin(function({ addUtilities, addComponents, e, prefix, config }) {
// Add your custom styles here
}),
]
})
This could be solved setting up the bundler to alias tailwindcss/plugin
to twind/plugin
. Preact has a good guide how to do that.
Importing plugins may work in bundler environment. But if used directly in the browser this may not work.
This is not really a challenge but points more to how using plugins could increase the bundle size.
It would be nice if we could use the server side rendering part, to style emails with this library.
The thing is, not all mail clients know how to handle style tags. The recommended approach is still to use inline styles (<div style="color: red">
).
I do realize that it's not possible to support everything with that approach, but even partial support could be of immense value when creating mail templates.
Things that are unsupported (like media queries) could still go to the head style sheet for those clients that do support it.
It would also mean that css variables should be resolved.
Thoughts?
Hey, super cool project that’s probably going to change a lot the way I currently use tailwind in some of my projects. I was already quite into Oceanwind, but this is really next level.
I quickly tried and put it head to head, reproducing the welcome example of https://play.tailwindcss.com/ into my favorite JS library and was blown away by how close it was… In fact, i’d even say it’s 1:1.
There’s one thing, however, that might be cool to add and I didn’t see any mention in the documentation. The extended color palette introduced in tailwind 2. As far as I understand, the default theme colors in twind is matching the default theme in tailwind, which only includes a small subset of colors. Here’s the full color list that was introduced in tailwind 2: https://github.com/tailwindlabs/tailwindcss/blob/master/colors.js.
For file size reason, I totally get that it shouldn’t be integrated within the default theme (that’s also probably why it wasn’t in tailwind by default, to trim down the compilation time), but I wonder if it could be possible to add like an extra exported object that contains those rules. e.g.:
import { setup, colors } from 'twind'
// or import colors from 'twind/colors';
setup({
theme: {
extend: {
colors: { cyan: colors.cyan }
}
}
})
The only problem I suppose is to get good tree shakeability from an object so that you only pay for the color you use… I’m not sure if that’d be possible. I guess you could also export each and every color separately:
import { red, cyan, blue } from 'twind/colors';
// or import * as colors from 'twind/colors';
setup({
theme: {
extend: {
colors: { red, cyan, blue }
}
}
})
That’s just a suggestion and I supposed one could also just copy and paste the the needed color from the original tailwind color palette as well.
Again, thanks for the library!
Are you open for a PR to add Next to https://github.com/tw-in-js/twind/blob/main/docs/examples.md ?
It took me a long time to figure out how to get this working in Next.js. The reason I got confused was because most React examples use below double quoted examples like shown below:
<main className="${tw`h-screen bg-purple-400 flex items-center justify-center`}">
<h1 className="${tw`font-bold text(center 5xl white sm:gray-800 md:pink-700)`}">
This is Twind!
</h1>
</main>
After removing the double quotes, things simply started working ❤️
<main className={tw`h-screen bg-purple-400 flex items-center justify-center`}>
<h1 className={tw`font-bold text(center 5xl white sm:gray-800 md:pink-700)`}>
This is Twind!
</h1>
</main>
For reference, in tailwind 2.0, when using the transition
property, it applies a default transition duration: https://play.tailwindcss.com/Ivax9nmjjL
twind doesn't seem to do the same: https://codesandbox.io/s/twind-default-transition-0wds7
Twind currently only supports string properties within the screens sections. For full compat we should add support for arrays and objects with raw, min or max properties.
See https://github.com/tailwindlabs/tailwindcss/blob/master/src/util/buildMediaQuery.js
setup({
theme: {
extend: {
screens: {
standalone: { raw: '(display-mode:standalone)' },
special: [ { min: '800px', max: '1000px' } ],
},
},
},
})
Hello!
Awesome work on this library, feels amazing! 🎉
In React, we have a scenario which we have troubles solving using tailwindcss, but feels like we should be able to solve it using something like this. Imagine a React component that has some tailwindcss classes defined in implementation, but also receives a className prop that should be able to override it.
An example:
function Button({ className = "", children }) {
return (
<button className={tw("border text-white bg-blue-600", className)}>{children}</button>
);
}
If I use this component as: <Button>blue</Button>
, button should be blue, but if I try to pass bg-red-600
as className
, <Button className="bg-red-600">red</Button>
, then it should be red. Because of the way classes are applied (based on order in stylesheet), this isn't possible without removing bg-blue-600
from className.
I wrote simple demo available here using twind that demonstrates simple scenario that doesn't work.
Is there a theoretical way to make this work with this approach? I'd be willing to contribute with a bit of guidance. :)
I have re-structured the documentation to have a good starting point to provide a better "on-boarding": https://twind.dev/docs/
I have splitted the documentation in to two parts:
docs/
folder (see docs/pages.json)The website is currently updated on every push to main. Later I would like to only update the website on releases to have a stable documentation for a release that is not mixed with upcoming features.
What needs to be done?
twind
I'm sure there is a lot more to improve. I hope that some native speakers may pick some of the tasks and provide PRs for these.
You can build the website locally using: yarn typedoc && open website/docs/index.html
/cc @tw-in-js/contributors
Please see this minimum example
import { tw } from "twind";
document.body.innerHTML = `
<div class="${tw("bg-pink-50", "hover:text-pink-900", "p-3")}">
Not array
</div>
<hr />
<div class="${tw(["bg-pink-50", "hover:text-pink-900", "p-3"])}">
Array
</div>
`;
Screenshot
Everything behind hover:
will be ignored when using tw(...)
I think it's reasonable to assume one might want to expand on or modify the default Tailwind preflight but native Tailwind doesn't currently provide a way to do this. So twind gets the luxury of doing it first! I'd like to open up a discussion about how that feature might look.
My first thought would be to possibly provide an extend
key which could behave similarly to theme.extend
.
Hi, thank you for this cool library!
I was working on a React app project, everything went fine until I wrote my own Tailwind configuration. The configuration I wrote down lost in the specificity battle (because of generated internal style).
My configuration:
const plugin = require('tailwindcss/plugin')
module.exports = {
purge: [],
darkMode: 'media', // or 'media' or 'class'
theme: {
extend: {
fontFamily: {
sans: [
'"Source Sans Pro"',
],
....
I found the internal style in DevTools which this library generates. In the documentation I found that to use the twind
function I didn't need the setup
function. How can I just use the twind
function to group Tailwind classes? Without any internal styles and my configuration works fine.
Thanks.
Please see this minimum markup and watch the different result between Tailwind Play and twind:
<div class="w-screen h-screen flex justify-center items-center">
<div class="shadow-lg w-24 h-24 bg-white rounded flex justify-center items-center">
<button class="rounded p-1 ring-1 ring-red-500">Click</button>
</div>
</div>
Result:
Code:
Twind's ring gets override by parent shadow.
My first thought was adding shadow-none
to revert this, but watch what happen when I add shadow-none
:
<div class="w-screen h-screen flex justify-center items-center">
<div class="shadow-lg w-24 h-24 bg-white rounded flex justify-center items-center">
<button class="shadow-none rounded p-1 ring-1 ring-red-500">Click</button>
</div>
</div>
Tailwind Play is still the same, but twind's ring color disappears!
The styled API is commonly used within the react community. Although twind is framework agnostic it may be nice to provide a styled module as well.
The API proposed here is inspired by emotion, styled-component and goober. The idea is to add two new modules:
The main discussion is about what type (CSS or tailwind) the styled API uses. Most editors support some CSS based styled component. Our apply
method can be used within css
and we could have the best of both worlds. That is why I'd like start fresh and propose the following API:
styled: thin wrapper around css
import { styled, theme } from 'twind/styled'
const Button = styled.button`
color: ${theme('colors.gray.500)};
`
To use Tailwind within styled the @apply
helper can be used:
import { styled, theme } from 'twind/styled'
const Button = styled.button`
color: ${theme('colors.gray.500)};
@apply bg-gray-50;
`
twind: thin wrapper around apply
To have tailwind styled component we could use a twind
export (#7 (comment)):
import { twind } from 'twind/styled'
const Button = twind.button`
text(base blue-600)
rounded-sm
border(& solid 2 blue-600)
m-4 py-1 px-4
`
Support for CSS would be available via css
:
import { twind, css } from 'twind/styled'
const Button = twind.button`
text(base blue-600)
${css`
some: css props;
`}
`
styled
and twind
components both used an style object internally and can be used together. They differ only in how the styles are defined.
Adapting based on props
const Button = styled.button`
color: ${(props, { theme }) => theme(props.primary ? 'colors.purple.600' : 'colors.blue.600')};
`
const Container = twind.div`flex ${(props) => props.column && 'flex-col'}`
Combine different styling #7 (comment)
// Passing a callback that receives props and returns list of various stylings
const Button = twind.button(({ primary }) => [
// tagged template
tw`font-bold`,
// plain string
'px-4 py-2 rounded-full',
// prop condition (no need for ${} interpolation)
primary ? 'bg-red-600 text-white' : 'bg-white text-gray-800',
// CSS object notation
css({ fontSize: '14pt' })
])
Styling any component
any component can be styled as long as it accepts a className
prop:
const Basic = ({ className }) => <div className={className}>Some text</div>
const Fancy1 = styled(Basic)`color: darkgreen;`
const Fancy2 = twind(Basic)`text-purple-600`
Extending Styles
const Button = twind.button`
text(base blue-600)
rounded-sm
border(& solid 2 blue-600)
m-4 py-1 px-4
`
const PurpleButton = twind(Button)`
text-purple-600 border-purple-600
`
as
prop
To use styles from a styled component but change the element that’s rendered, you can use the as
prop.
const Button = twind.button`text-purple-600`
render(
<Button as="a" href="https://github.com/tw-in-js/twind">
Twind on GitHub
</Button>,
)
The as
prop is only used by styled when it’s not forwarded to the underlying element. By default, this means that the as
prop is used for html tags and forwarded for components. To change this, you can pass a custom shouldForwardProp
which returns true
for 'as'
to forward it or returns false
for 'as'
to use it and not forward it.
Attaching additional props
To avoid unnecessary wrappers that just pass on some props to the rendered component, or element, you can use the .attrs
function. It allows you to attach additional props (or "attributes") to a component.
// we can define static props
const Input = twind.input.attrs({
type: 'text',
})`
rounded-md
border(& 4 indigo-600)
`
// Or we can define dynamic ones
const SizedInput = Input.attrs((props) => ({
size: props.size || '1em',
}))
Notice that when wrapping styled components, .attrs
are applied from the innermost styled component to the outermost styled component.
This allows each wrapper to override nested uses of .attrs
, similarly to how css properties defined later in a stylesheet override previous declarations.
const PasswordInput = twind(Input).attrs({
type: 'password',
})`
border-blue-600
`
className
andclass
properties are merged into one string. They do not override each other.
Referring to other components
We can nest selectors using &
:
const Child = twind.div`text-purple-600`
const Parent = styled.div(
css({
[`& ${Child}`]: {
color: 'green',
},
}),
)
Currently this can only work when using CSS (either
styled
orcss
withintwind
)
shouldForwardProp
styled
is smart enough to filter non-standard attributes automatically for you. By default, styled
passes all props to custom components and only props that are valid html attributes for string tags.
You can customize this by passing a custom shouldForwardProp
function. It works much like the predicate callback of Array.filter
. A prop that fails the test isn't passed down to underlying components.
Optionally, shouldForwardProp
can take a second parameter that provides access to the default validator function (@emotion/is-prop-valid
) which is used by styled internally. This function can be used as a fallback, and of course, it also works like a predicate, filtering based on known HTML attributes.
const H1 = twind('h1', {
shouldForwardProp: (prop, defaultValidator) => prop !== 'hidden' && defaultValidator(prop),
})`
text-2xl underline
${(props) => props.hidden && 'sr-only'}
`
render(<H1 hidden>Screen Reader Only</H1>)
Refs
Passing a ref
prop to a styled component will give you one of two things depending on the styled target:
twind.div
)React.Component
)const Input = twind.input`
rounded-sm m-2 p-2
text-pink-500
bg-yellow-200
`
const Form = () => {
const inputRef = useRef()
return (
<Input
ref={inputRef}
placeholder="Hover to focus!"
onMouseEnter={() => {
inputRef.current.focus()
}}
/>
)
}
Using custom tw instance
styled
uses the named tw
export from the twind
module. You can use a custom instance:
import { create } from 'twind'
import { styled } from 'twind/styled'
const { tw } = create(/* addtional config */)
const myStyled = styled.bind(tw)
/cc @tw-in-js/contributors
This is probably just an issue with Skypack, but it seems like there's an issue w/ the module being served. The demos all appear broken and have this error:
Uncaught TypeError: Cannot destructure property 'cssPropertyAlias' of '__commonjs_module0' as it is null.
I've noticed a few TypeScript issues with the styled
API for React:
type
prop on Input )any
)I’ve included a minimal example that demonstrates each of these issues. It also includes a styled-components
implementation for comparison.
Codesandbox Link: https://codesandbox.io/s/twind-styled-typescript-by9l4
Currently new plugins are added via the plugins
object which are merge with the builtin plugins:
setup({
plugins: {
'scroll-snap': () => { /* ... */ }
},
})
Some modules (like typography eg prose) need to add several plugins and may add a new theme section.
We are thinking about supporting this using a new configuration property:
setup({
presets: [
function typographyPreset({ theme, plugins }) {
return {
theme: {
typography: {
textColor: theme("colors.gray.600"),
linkColor: theme("colors.blue.600"),
},
},
plugins: {
lead: () => {
/* ... */
},
prose: () => {
/* ... */
},
},
};
},
],
});
A preset has access to the current constructed configuration via its first parameter and should return a new partial configuration which will be merged with the current configuration.
As an alternative we could allow setup
to accept a function which is called with the defaults and provide a withPresets
function (maybe as @tw-in-js/with-presets
)
setup(
withPresets(
function typographyPreset({ theme, plugins }) {
return {
theme: {
typography: {
textColor: theme("colors.gray.600"),
linkColor: theme("colors.blue.600"),
},
},
plugins: {
lead: () => {
/* ... */
},
prose: () => {
/* ... */
},
},
};
},
));
Using this withPresets
this basic implementation which can be improved separately:
function withPresets(...presets) {
return (config) => presets.reduce((config, preset) => ({ ...config, ...preset(config) }), config)
}
Hi Guys.
I'm working with twind and looks pretty good, but I have a question.
Exist a way to parse to styles string with classes reference?
I've used getStyleTag
but it returns a style string with its elements and I would to know if it could set classes instead.
Thanks for your job!
These are all available on npm:
The next ones are squatted. We could reach out to npm for these.
here https://codesandbox.io/s/busy-moon-ypndy?file=/src/App.tsx:96-118 I set class container md:max-w-3xl
which works in tailwind like that: it uses container class for screens smaller than md and uses fixed width on md and bigger.
You can test that it works in tailwind as expected if you comment this line
https://codesandbox.io/s/busy-moon-ypndy?file=/public/index.html:1033-1110
and uncomment this one
https://codesandbox.io/s/busy-moon-ypndy?file=/public/index.html:1114-1238
So far it seems like twind works with client side Deno. Either shim or regular (I also tested https://cdn.esm.sh/twind
, https://jspm.dev/twind
). This is not that surprising given that twind already works as es module in the browser :)
import 'https://cdn.skypack.dev/twind/shim'
import { tw, setup } from 'https://cdn.skypack.dev/twind'
As for server-side support, the first issue is that it just crashes :)
https://cdn.esm.sh/twind/server
https://jspm.dev/twind/server
https://cdn.skypack.dev/twind/server
I guess it happens because of dependency on async rendering for node.
So I tried to create a simple server on Deno that could run on codesanbox https://codesandbox.io/s/deno-twinddev-ssr-p4l2h?file=/src/index.ts
This one is actually not that bad :) it crashes if I try to update to the latest version, but otherwise does most of what I expected twind/server
to do :)
p.s. I personally was trying it out in aleph.js but it comes with its own complications that probably would require a separate issue :)
I'd submit a pr for this but it involves the live demo for observe so it probably needs to be updated there.
It works again when
import { observe } from 'twind/observe'
is changed to
import { observe } from 'https://cdn.skypack.dev/twind/observe'
To complicate the theft of components that have been made, I usually obfuscate the existing class names, so that the thief has difficulty refactoring the code he took on the original website.
But, this library is run at runtime, I am confused about how to obfuscate the existing class names. Do you have any suggestions?
Input
tw`text-gray-100 bg-green-200 relative`
Output
"a b c"
text-gray-100
has been obfuscated to a
bg-green-200
has been obfuscated to b
relative
has been obfuscated to c
The obfuscating process above is only doing in the Production stage.
Thanks.
When used in an SSR context, getStyleTag
is designed to produce a <style>
tag suitable for embedding in generated HTML so that the initial payload has all critical CSS inlined.
The act of embedding anything into server-generated HTML introduces an XSS risk. That risk is proportional to the likelihood that user-submitted content might find its way into the generated markup. In the general case, this risk seems quite low for a tool like twind
.
That being said, I suspect some creative folks will realize that twind
is an unusually effective way of rendering similar markup with different themes in the same page. They may also think that it would be interesting to allow users to create and share their own themes. At this point we're pretty far into the realm of the hypothetical and should think about balancing risk vs cost.
Below, I present some links where similar issues have been brought for other frameworks living in the same space as twind
.
My conclusion is that on the balance of risk and cost, a strategic replacement of </
(and maybe html comments?) would go a long way in closing the XSS window at a very small cost.
styled-components
: styled-components/styled-components#1828.styled-components
: styled-components/styled-components#1105 (comment)jss
: cssinjs/jss#1265When injecting JavaScript of JSON into HTML, my go-to solution is jsesc.
So since the current approach to declaring global styles seems to be best accomplished through preflight, it would be nice to provide some kind of merging strategy for common selector keys as to avoid this sort of code...
preflight: ({ html, ...preflight }, { theme }) => ({
...preflight,
html: {
color: theme('colors.gray.900'),
...html,
},
})
Otherwise, I think there should be some discussion about ways to declare global styles.
Regarding this new (:global)[https://github.com/tw-in-js/twind/blob/main/docs/plugins.md#inject-global-styles] property, is it at all possible to declare global styles from within inline plugins? That would be a nifty way to allow declaration of global styles from virtually anywhere in your app.
Repo: https://github.com/itsMapleLeaf/twind-specificity-issue
Where the text says "this element should be translucent, but is not", that has bg-black bg-opacity-25
on it, but the bg opacity isn't applying due to the specificity of bg-black
's default opacity.
Here's the code that reproduces this:
export default function Home() {
const [isReady] = useTimeout(100)
return (
<>
<Portal>
<div className={tw`bg-black text-white`}>portal element</div>
</Portal>
{isReady() && (
<div className={tw`bg-black bg-opacity-25 text-white`}>
this should be translucent, but is not?
</div>
)}
</>
)
}
It happens only with the Portal
on the first element, and a delayed render of the second. If anything about this changes, then the issue goes away. Very interesting and strange 🤔
Currently, Tailwind doesn't support nested groups, a couple of discussions are going on here
I've found a plugin to solve this, so the syntax becomes group-foo
or group-bar
https://github.com/ErickTamayo/tailwindcss-named-groups
Source: #61
The mysterious case of .sticky
lives on! lukejacksonn/oceanwind#43
For whatever reason sticky
creates an empty translation in the browser. I spotted this back when using oceanwind, and after testing it again tonight, it bizarrely still fails to work. All other positioning shorthand work as expected though.
I found this library in my search for runtime tailwind.
My use case is basically this that I would like to have a set of default tailwind styles added to the application beforehand and then when the user wants to change the style of an item at runtime, use twind to update the existing style tag.
I would like to prevent the import of tailwind classes with twind if I already have some classes packaged in my app.
Question is, is it possible to do such a thing with this library?
The shim currently works only in browser environments. To support static extraction it must detect in which environment it runs and parse/analyze the generate html to inject the needed styles.
As a first use case we will try to support wmr prerender as it return a string of html which should be analyzable.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.