Security News
JSR Working Group Kicks Off with Ambitious Roadmap and Plans for Open Governance
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
tailwind-merge
Advanced tools
The tailwind-merge npm package is a utility for combining and deduplicating Tailwind CSS classes. It helps developers to programmatically merge class strings while avoiding conflicts and redundancies, ensuring the final class string is optimized for use.
Merging Tailwind CSS classes
This feature allows you to merge multiple class strings into one, automatically resolving conflicts by using the last occurrence of a conflicting class.
"twMerge('p-4 font-bold', 'p-2 text-center') // 'p-2 font-bold text-center'"
Removing Tailwind CSS classes
This feature enables you to remove specific classes from a string by prefixing the class with a minus sign.
"twMerge('p-4 font-bold', '-p-4') // 'font-bold'"
Handling arbitrary values
Tailwind-merge can handle arbitrary values in class names, allowing you to merge classes with custom values effectively.
"twMerge('p-[30px]', 'p-4') // 'p-4'"
Handling variants and groups
The package can also manage Tailwind's variants and groups, ensuring that the correct specificity is maintained when merging classes with variants.
"twMerge('hover:bg-black', 'hover:bg-white') // 'hover:bg-white'"
The classnames package is a popular utility for conditionally joining class names together. It's not Tailwind-specific, but it's often used in projects to manage dynamic class strings. Unlike tailwind-merge, it does not deduplicate or resolve conflicts specific to Tailwind CSS.
clsx is an alternative to classnames with a similar API and is used for constructing class strings conditionally. Like classnames, it does not offer Tailwind-specific deduplication or conflict resolution.
Utility function to efficiently merge Tailwind CSS classes in JS without style conflicts.
import { twMerge } from 'tailwind-merge'
twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]')
// → 'hover:bg-dark-red p-3 bg-[#B91C1C]'
If you use Tailwind with a component-based UI renderer like React or Vue, you're probably familiar with this situation:
import React from 'react'
function MyGenericInput(props) {
const className = `px-2 py-1 ${props.className || ''}`
return <input {...props} className={className} />
}
function MySlightlyModifiedInput(props) {
return (
<MyGenericInput
{...props}
className="p-3" // ← Only want to change some padding
/>
}
When the MySlightlyModifiedInput
is rendered, an input with the className px-2 py-1 p-3
gets created. But because of the way the CSS cascade works, the styles of the p-3
class are ignored. The order of the classes in the className
string doesn't matter at all and the only way to apply the p-3
styles is to remove both px-2
and py-1
.
This is where tailwind-merge comes in.
function MyGenericInput(props) {
// ↓ Now `props.className` can override conflicting classes
const className = twMerge('px-2 py-1', props.className)
return <input {...props} className={className} />
}
tailwind-merge makes sure to override conflicting classes and keeps everything else untouched. In the case of the MySlightlyModifiedInput
, the input now only renders the class p-3
.
I didn't run any performance benchmarks so far, but you should be able to merge a lot of classes per second. Some aspects of the library:
createTailwindMerge()
.twMerge('p-5 p-2 p-4') // → 'p-4'
twMerge('p-3 px-5') // → 'p-3 px-5'
twMerge('inset-x-4 right-4') // → 'inset-x-4 right-4'
twMerge('inset-x-px -inset-1') // → '-inset-1'
twMerge('bottom-auto inset-y-6') // → 'inset-y-6'
twMerge('inline block') // → 'block'
twMerge('p-2 hover:p-4') // → 'p-2 hover:p-4'
twMerge('hover:p-2 hover:p-4') // → 'hover:p-4'
twMerge('hover:focus:p-2 focus:hover:p-4') // → 'focus:hover:p-4'
twMerge('bg-black bg-[color:var(--mystery-var)]') // → 'bg-[color:var(--mystery-var)]'
twMerge('grid-cols-[1fr,auto] grid-cols-2') // → 'grid-cols-2'
twMerge('!p-3 !p-4 p-5') // → '!p-4 p-5'
twMerge('!right-2 !-inset-x-1') // → '!-inset-x-1'
twMerge('p-5 p-2 my-non-tailwind-class p-4') // → 'my-non-tailwind-class p-4'
twMerge('text-red text-secret-sauce') // → 'text-secret-sauce'
Reference to all exports of tailwind-merge.
twMerge
function twMerge(...classLists: Array<string | undefined>): string
Default function to use if you're using the default Tailwind config or are close enough to the default config. You can use this function if all of the following points apply to you Tailwind config:
If some of these points don't apply to you, it makes sense to test whether twMerge()
still works as intended with your custom classes. Otherwise, you can create your own custom merge function with createTailwindMerge()
.
createTailwindMerge
function createTailwindMerge(createConfig: CreateConfig): TailwindMerge
Function to create merge function with custom config.
You need to provide a function which resolves to the config tailwind-merge should use for the new merge function. You can either extend from the default config or create a new one from scratch.
const customTwMerge = createTailwindMerge((getDefaultConfig) => {
const defaultConfig = getDefaultConfig()
return {
cacheSize: 0, // ← Disabling cache
prefixes: [
...defaultConfig.prefixes,
'my-custom-prefix', // ← Adding custom prefix
],
// ↓ Here you define class groups with a common start in all class names
dynamicClasses: {
...defaultConfig.dynamicClasses,
// ↓ It's important that keys at this level don't have dashes in them.
foo: [
// ↓ Creates group of classes which have conflicting styles
// Classes here: foo-1, foo-2, foo-bar-baz-1, foo-bar-baz-2
['1', '2', { 'bar-baz': ['1', '2'] }],
],
bar: {
// ↓ Another group with classes bar-auto, bar-1000, bar-1001, …
// Groups can be named to make referencing in conflictingGroups easier
namedGroup: ['auto', (value) => Number(value) > 1000],
},
},
// ↓ Same like `dynamicClasses`, just for classes with no common starting characters
standaloneClasses: [
...defaultConfig.standaloneClasses,
// ↓ Same structure like in `dynamicClasses`, but only strings allowed
['my-custom-class', 'other-class-same-group'],
],
// ↓ Here you can define additional conflicts across different groups
conflictingGroups: {
...defaultConfig.conflictingGroups,
// ↓ Path to class group which creates a conflict with …
'dynamicClasses.foo.0': [
// ↓ … classes from group at this path
'dynamicClasses.bar.namedGroup',
],
},
}
})
This package follows the SemVer versioning rules. More specifically:
Patch version gets incremented when unintended behaviour is fixed which doesn't break any existing API. Note that bug fixes can still alter which styles are applied. E.g. a bug gets fixed in which the conflicting classes inline
and block
weren't merged correctly so that both would end up in the result.
Minor version gets incremented when additional features are added which don't break any existing API. However, a minor version update might still alter which styles are applied if you use Tailwind features not yet supported by tailwind-merge. E.g. a new Tailwind prefix magic
gets added to this package which changes the result of twMerge('magic:px-1 magic:p-3')
from magic:px-1 magic:p-3
to magic:p-3
.
Major version gets incremented when breaking changes are introduced to the package API. E.g. the return type of twMerge()
changes.
alpha
releases might introduce breaking changes on any update. Whereas beta
releases only introduce new features or bug fixes.
Releases with major version 0 might introduce breaking changes on a minor version update.
All changes are documented in GitHub Releases. If a release can alter styles being applied, a warning is issued in the release description.
FAQs
Merge Tailwind CSS classes without style conflicts
The npm package tailwind-merge receives a total of 3,239,184 weekly downloads. As such, tailwind-merge popularity was classified as popular.
We found that tailwind-merge demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Security News
Research
An advanced npm supply chain attack is leveraging Ethereum smart contracts for decentralized, persistent malware control, evading traditional defenses.
Security News
Research
Attackers are impersonating Sindre Sorhus on npm with a fake 'chalk-node' package containing a malicious backdoor to compromise developers' projects.