Repeat elements without repeating styles
*.child
, .sibling
, and .descendant*
variants for TailwindCSS v3+
- Install package:
npm install tailwind-children --save
- Add to tailwind.config.js
plugins: [
require('tailwind-children'),
]
- Build tailwind:
npx tailwindcss -i ./src/input.css -o ./dist/output.css
Styles in parent prefixed with child
will apply to all children.
Aliases: children
can be used instead of child
.
Do we need both
child
andchildren
aliases? Join the discussion.
<!-- apply to all children with matching .child class -->
<div class="child:ring-white">
<p>I have a white ring.</p>
</div>
- Modifiers such as :hover can be applied to children, but should be placed before the
child
variant. - Rules can be limited (child-p targets
<p>
), excluded (.not-child-p
) or overridden, see below.
<!-- apply to <p> elements, with shadow on hover -->
<div class="child-p:ring-white hover:child-p:border">
<p>I have a white ring, and a border on hover.</p>
<b>I am ignored!</b>
</div>
Same usage, but includes non-direct descendants.
Aliases: descendant
or heir
.
Is
heir
a fair name for a variant? Join the discussion.
<div class="heir-p:ring-white hover:descendant-p:shadow">
<div>
<p>I have a white ring...</p>
</div>
<div>
<p>And a shadow on hover!</p>
<b>I am not a `p`, so am ignored.</b>
</div>
</div>
Same basic usage, but is applied to first of the repeating elements.
All styles are applied both to itself and to sibling elements unless overridden.
Aliases: twin
, sibling
.
Do we need both
sibling
andtwin
? Should they be treated differently, withtwin
applied to itself as well, andsibling
only applied to siblings? Add your ideas and feelings to the discussion.
<div>
<p class="twin:ring-white hover:twin:shadow">
I have a white ring & shadow on hover
</p>
<p>I am his twin, so have the same!</p>
</div>
TailwindCSS is a GREAT utility-first CSS framework. Instead of inline styles (<p style='display:block'>
) or opinionated predefined classes (<button class='btn'>
) you have classes that replace and enhance the inline styles (<p class='block'>
).
Its biggest issue is that it is (to quote them) "ugly". Your page gets littered with hundreds or thousands of classes. That ugliness is justified though, and once you start using TW you will never go back.
But there is one case where the ugliness is not just extreme, it also slows dev, adds complexity and introduces errors. It is such an issue, that it is referred to again and again in the official docs, has a page to itself - and the suggested solutions are pretty poor; use an editor plugin or rely on JS.
The issue is that of repeating elements (such as <li>'s
) that use the same styles.
And its an issue which is surprising, as Tailwind's class based nature should make it really easy to fix.
This, then, is an attempt to solve the issue of reusing styles through a plugin, and maybe @adam will see this and make it official :)
To copy the example loosely from https://tailwindcss.com/docs/reusing-styles.
Let's say you want to experiment with adding a shadow to all your <img>
s.
Manually copying it in each one is slowly and buggy. What to do?
<div class="mt-3 flex -space-x-2 overflow-hidden">
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white hover:shadow" src="/img0.jpg">
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white hover:shadow" src="/img1.jpg">
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white hover:shadow" src="/img2.jpg">
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white hover:shadow" src="/img3.jpg">
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white hover:shadow" src="/img4.jpg">
</div>
Use heir
, child
, or sibling
variants to target the respective elements (see usage above).
<div class="mt-3 flex -space-x-2 overflow-hidden
child:inline-block child:h-12 child:w-12 child:rounded-full child:ring-2 child:ring-white hover:child:shadow">
<img src="/img0.jpg">
<img src="/img1.jpg">
<img src="/img2.jpg">
<img src="/img3.jpg">
<img src="/img4.jpg">
</div>
Isn't that nice?
Rules are applied to all children, descendants and siblings, but can be overridden or limited:
a. Rules on children have precedence over rules on the parent.
<div class="child:shadow">
<p>Shadow</p>
<p class="drop-shadow-none">No Shadow</p>
</div>
b. class .not-child{-element} excludes element.
<div class="child:shadow">
<p>Shadow</p>
<p class="not-child">No Shadow</p>
</div>
c. Limit children via CSS selector. The syntax is `child-['$selector']`.
Not yet implemented.
<div class="child-['.child']:shadow">
<p class="child">Shadow</p>
<p>No Shadow</p>
</div>
d. Limit children via tag, with syntax `child-$tag`; Shorthand for `child-['$tag']`.
[ Until arbitrary css is available, one can use any element tag. This will later be reduced to common tags (a, b, i, p, u, div, span, ul, ol, li). ]
<div class="child-p:shadow">
<p>Shadow</p>
<a>No Shadow</a>
</div>
Add class .rules-applied-to-siblings
(or similar) to the first element, and that will apply the same rules to all of the siblings.
This will make it simple to experiment, easy to understand, is clean and DRY. FTW!
<div class="mt-3 flex -space-x-2 overflow-hidden">
<img class="rules-applied-to-siblings inline-block h-12 w-12 rounded-full ring-2 ring-white hover:shadow" src="/img0.jpg">
<img src="/img1.jpg">
<img src="/img2.jpg">
</div>
Even better, we can have siblings-of-type
to have it only apply said classes to peer elements with same tag.
Admittedly, I do not know of any similar existing TW rule, but maybe it is just a matter of no need.
Tailwind doesn't use other custom attributes, but I don't see why they shouldn't. CSS can handle any selectors, and we can prefix with tw- if we worry about conflict.
We can therefore create a children
attribute with what ought to be applied to the children:
<div class="mt-3 flex -space-x-2 overflow-hidden"
children="inline-block h-12 w-12 rounded-full ring-2 ring-white hover:shadow">
<img src="/img0.jpg">
<img src="/img1.jpg">
</div>
This would make a rather awkward css rule, but not one that affects the dev or the user:
[children="siblings inline-block h-12 w-12 rounded-full ring-2 ring-white hover:shadow"] > * { ... }
To be sure, I think that Tailwind should offer this for all psuedo-classes:
<div class="block"
tw-after="content-['wow'] text-white">...
</div>
And it is my hope that this plugin might be a foray into such usage!
Modifiers such as :hover must be set first (eg. hover:child:shadow) for it to be applied to the children.
In earlier versions, we created custom variants for all modifiers, so that you can use the form child-hover
, which is easier to read, and matches group and peer:
<input class="peer">
<p class="peer-hover:shadow">p has shadow when hovering on input</p>
<div class="group">
<p class="group-hover:shadow">p has shadow when hovering over div</p>
</div>
However, this was removed as:
- It is unnecessary: unlike group whose hover affect another element, and needs a special variant.
- It is unclear: child-p (a limitation) and child-hover (a psuedo class) would use the same format.
- It doesn't match: the official typography plugin
- we were missing states, and :hover:child is more complete.
Earlier versions limitted matching children to those havng a class .child
.
Basically opt-in to have a style applied.
This matched the styles in peer, group, theme, etc.
<body class="dark">
<p class="dark:shadow">p has shadow when dark theme enabled</p>
</body>
<input class="peer">
<p class="peer-hover:shadow">p has shadow when hovering on input</p>
<div class="group">
<p class="group-hover:shadow">p has shadow when hovering over div</p>
</div>
However, we now offer much more flexibility with limitations and overrides, and the old sytax was usually not the desired one, so we dropped it. Besides, this matches typography. :)
Please open issues, file bug reports, give me your opinions on variant names, default styles and behaviors, and whatever else you can think of. There are a lot of good things input can add!
tailwind-children is a ES6 module that is backported to CommonJS for use with taiwindcss (which is in CommonJS format).
Originally, we used require-esm-in-cjs to setup a simple CJS wrapper around the ES6 module, but this seemed to have created an issue for Vite users
Until that can be resolved, am using esbuild (installed globally) to export to CJS for production as follows:
esbuild ./src/index.mjs --outdir=dist --format=cjs
echo "module.exports = src_default;" >> "./dist/index.js"
To run tailwind and test visually:
npx tailwind -o ./tests/test.css -c ./tests/tailwind.config.cjs -i ./tests/input.css
To run tests in JEST:
npm run test
The docs for working with variants are not at all together, but here are some useful links:
- addVariant API: conversation, commit message, discussion
- Official Docs: Plugins - addVariant(), Writing Plugins
- tailwindcss source: createPlugin.js - is called by wrapper plugin.js, corePlugins.js, variants.test.js, list of variants
- postcss-selector-parser API docs
- To Adam Wathan and Jonathan Reinink for creating and promoting Tailwind, and for the @tailwindcss/typography plugin.
- To RobinMalfait for his outstanding work on the variant API.
- To Benoît Rouleau for tailwindcss-children and Stefan Racic for tailwind-child - two projects that predate ours and provided inspiration.
- To Micah Effiong and Eric Pedia and every one else who has helped!
Please use, star, spread the word, give ideas, contribute!