Last active
June 17, 2025 18:24
-
-
Save Neophen/2f512ace1e7182e5346076333e4a0fdc to your computer and use it in GitHub Desktop.
Revisions
-
Neophen revised this gist
Jun 17, 2025 . 5 changed files with 49 additions and 131 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,27 +6,24 @@ https://gist.github.com/user-attachments/assets/4af1f288-6c64-41e0-8765-56fb8e5d ## Tailwind Play link: [https://play.tailwindcss.com/7HbSJlaQGT](https://play.tailwindcss.com/7HbSJlaQGT) # Use case: A card with main action and secondary action https://gist.github.com/user-attachments/assets/420b6008-965c-424a-a610-6544a1774804 ## HTML if the link is broken ```html <div class="grid gap-6 bg-slate-100 p-4"> <h1>Use case: card with main link and other actions</h1> <div class="rounded-md border bg-white p-4"> <div class="group/item relative flex items-center justify-between rounded-md border p-4 hovered-action:bg-blue-100 hovered-action:ring-2 hovered-action:ring-black pressed-action:bg-blue-600"> <p class="font-bold group-hovered-action/item:text-blue-600 group-pressed-action/item:text-white">Mykolas Mankevicius</p> <div class="flex gap-4"> <button type="button" class="peer z-10 block rounded border bg-white px-2 py-1 hovered:bg-red-50 hovered:text-red-900 pressed:bg-red-200">Remove</button> <button type="button" class="action block rounded border bg-slate-600 px-2 py-1 text-white before:absolute before:inset-0 hovered:bg-slate-900">View Profile</button> <div class="peer-hovered:bg-amber-50 peer-pressed:bg-amber-100">peer-pressed = bg-amber-100</div> </div> </div> </div> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,35 @@ @import "tailwindcss"; @custom-variant hovered { @media (hover: hover) { &:hover, &:focus-visible, &:has(:focus-visible) { @slot; } } } @custom-variant hovered-action { @media (hover: hover) { &:has(.action:hover), &:has(.action:focus-visible) { @slot; } } } @custom-variant pressed { @media (hover: hover) { &:active { @slot; } } } @custom-variant pressed-action { @media (hover: hover) { &:has(.action:active) { @slot; } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,42 +1,13 @@ <div class="grid gap-6 bg-slate-100 p-4"> <h1>Use case: card with main link and other actions</h1> <div class="rounded-md border bg-white p-4"> <div class="group/item relative flex items-center justify-between rounded-md border p-4 hovered-action:bg-blue-100 hovered-action:ring-2 hovered-action:ring-black pressed-action:bg-blue-600"> <p class="font-bold group-hovered-action/item:text-blue-600 group-pressed-action/item:text-white">Mykolas Mankevicius</p> <div class="flex gap-4"> <button type="button" class="peer z-10 block rounded border bg-white px-2 py-1 hovered:bg-red-50 hovered:text-red-900 pressed:bg-red-200">Remove</button> <button type="button" class="action block rounded border bg-slate-600 px-2 py-1 text-white before:absolute before:inset-0 hovered:bg-slate-900">View Profile</button> <div class="peer-hovered:bg-amber-50 peer-pressed:bg-amber-100">peer-pressed = bg-amber-100</div> </div> </div> </div> </div> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,73 +0,0 @@ This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,12 +0,0 @@ -
Neophen revised this gist
Dec 20, 2024 . 2 changed files with 63 additions and 147 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,77 +1,73 @@ import plugin from 'tailwindcss/plugin' type MaybeString = string | null | undefined type UtilType = 'group' | 'peer' // Example // <div class="group relative hovered-action:bg-blue-100 pressed-action:bg-blue-600"> // <p class="group-hovered-action:text-blue-600 group-pressed-action:text-white">Mykolas Mankevicius</p> // <button class="hovered:bg-red-50 hovered:text-red-600 z-10">Remove</button> // <button data-action class="before:absolute before:inset-0 hovered:bg-slate-100">View Profile</button> // </div> // Main plugin export default plugin((api) => { const betterStates = api.theme('betterStates', {}) const values = { values: { DEFAULT: '', ...betterStates } } const selector = (state: string, prefix: MaybeString) => { return `${prefix ?? '&'}:${state}:not(:disabled)` } const maybeWrap = (state: string, prefix: string, value: MaybeString) => { return ['', null, undefined].includes(value) ? selector(state, prefix) : `${prefix}:has(${selector(state, value)})` } const merge = ( type: UtilType, value: MaybeString, modifier: string | null, callback: (value: MaybeString, prefix: string, merge: boolean) => string[] ) => { const typeClass = modifier ? `.${type}\\/${api.e(modifier)}` : `.${type}` const append = type === 'group' ? ' &' : ' ~ &' return callback(value, `:merge(${typeClass})`, true).map((x) => `${x}${append}`) } // Hovered ________________________________________________________________________ const hovered = (value: MaybeString = null, prefix = '&', merge = false) => { const postfix = merge ? ' & ' : '' return [ `@media (hover: hover) and (pointer: fine) { ${maybeWrap('hover', prefix, value)}${postfix}}`, `@media (hover: hover) and (pointer: fine) { ${maybeWrap('focus-visible', prefix, value)}${postfix}}`, `@media (hover: hover) and (pointer: fine) { ${maybeWrap('has(:focus-visible)', prefix, value)}${postfix}}` ] } api.matchVariant('hovered', (value) => hovered(value), values) api.matchVariant('group-hovered', (value, { modifier }) => merge('group', value, modifier, hovered), values) api.matchVariant('peer-hovered', (value, { modifier }) => merge('peer', value, modifier, hovered), values) // Pressed ________________________________________________________________________ const pressed = (value: MaybeString = null, prefix = '&', merge = false) => { const postfix = merge ? ' & ' : '' return [ maybeWrap('active', prefix, value), maybeWrap('has(:active)', prefix, value), // Wrap the entire selector in the media query, not just the state `@media (hover: none) or (pointer: coarse) { ${maybeWrap('hover', prefix, value)}${postfix}}`, `@media (hover: none) or (pointer: coarse) { ${maybeWrap('focus-visible', prefix, value)}${postfix}}`, `@media (hover: none) or (pointer: coarse) { ${maybeWrap('has(:focus-visible)', prefix, value)}${postfix}}` ] } api.matchVariant('pressed', (value) => pressed(value), values) api.matchVariant('group-pressed', (value, { modifier }) => merge('group', value, modifier, pressed), values) api.matchVariant('peer-pressed', (value, { modifier }) => merge('peer', value, modifier, pressed), values) }) This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,80 +0,0 @@ -
Neophen revised this gist
Nov 5, 2024 . 2 changed files with 10 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -56,8 +56,11 @@ export default plugin(function ({ matchVariant, theme, e }) { const pressed = (value: MaybeString = null, prefix = "&") => [ maybeWrap("active", prefix, value), maybeWrap("has(:active)", prefix, value), `@media (hover: none) { ${maybeWrap('hover', prefix, value)} & }`, `@media (hover: none) { ${maybeWrap('focus-visible', prefix, value)} & }`, `@media (hover: none) { ${maybeWrap('has(:focus-visible)', prefix, value)} & }`, `@media (hover: none) { ${maybeWrap('focus', prefix, value)} & }`, `@media (hover: none) { ${maybeWrap('has(:focus)', prefix, value)} & }` ]; matchVariant("pressed", (value) => pressed(value), values); This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -57,8 +57,11 @@ export default { const pressed = (value = null, prefix = '&') => [ maybeWrap('active', prefix, value), maybeWrap('has(:active)', prefix, value), `@media (hover: none) { ${maybeWrap('hover', prefix, value)} & }`, `@media (hover: none) { ${maybeWrap('focus-visible', prefix, value)} & }`, `@media (hover: none) { ${maybeWrap('has(:focus-visible)', prefix, value)} & }`, `@media (hover: none) { ${maybeWrap('focus', prefix, value)} & }`, `@media (hover: none) { ${maybeWrap('has(:focus)', prefix, value)} & }` ] matchVariant('pressed', (value) => pressed(value), values) -
Neophen revised this gist
Nov 2, 2024 . 1 changed file with 34 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,34 @@ # Better hover/active states, Tailwind CSS ## Preview: https://gist.github.com/user-attachments/assets/4af1f288-6c64-41e0-8765-56fb8e5da29c ## Tailwind Play link: [https://play.tailwindcss.com/Aj7D2OHw20](https://play.tailwindcss.com/Aj7D2OHw20) # Use case: A card with main action and secondary action https://gist.github.com/user-attachments/assets/420b6008-965c-424a-a610-6544a1774804 ## Tailwind Play link: https://play.tailwindcss.com/6oReKHMiQG ## HTML if the link is broken ```html <div class="grid gap-6 bg-slate-100 p-4"> <h1>Use case: card with main link and other actions</h1> <div class="rounded-md border bg-white p-4"> <div class="group relative flex items-center justify-between rounded-md border p-4 hovered-action:bg-blue-100 hovered-action:ring-2 hovered-action:ring-black pressed-action:bg-blue-600"> <p class="font-bold group-hovered-action:text-blue-600 group-pressed-action:text-white">Mykolas Mankevicius</p> <div class="flex gap-4"> <button type="button" class="block rounded border bg-white px-2 py-1 hovered:bg-red-50 hovered:text-red-600 z-10">Remove</button> <button type="button" class="action block rounded border bg-slate-600 px-2 py-1 text-white before:absolute before:inset-0 hovered:bg-slate-900">View Profile</button> </div> </div> </div> </div> ``` -
Neophen created this gist
Nov 2, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,42 @@ <div class="grid gap-6 bg-slate-100 p-4"> <div class="grid gap-4 rounded-md border bg-white p-4 pressed-action:text-white"> <div class="group peer grid gap-2 rounded-md border p-4 hovered-action:bg-blue-100 pressed-action:bg-blue-500"> <h2 class="font-bold">group - peer - group-hovered-action</h2> <button type="button" class="action block rounded border bg-amber-200 px-2 py-1 hovered:bg-amber-400">Action</button> <div class="rounded bg-teal-100 p-2 group-hovered-action:bg-teal-500 group-pressed-action:bg-teal-800">I change state when <span class="inline-block rounded p-1 font-bold">Group > Action</span> is hovered/pressed</div> <div class="rounded bg-red-500 p-2 text-white">I don't change</div> </div> <div class="rounded bg-green-100 p-2 peer-hovered-action:bg-green-500 peer-pressed-action:bg-green-800">I change state whn <span class="inline-block rounded p-1 font-bold">Peer > Action</span> is hovered/pressed</div> <div class="rounded bg-indigo-100 p-2 peer-hovered:bg-indigo-500 peer-pressed:bg-indigo-800">I change states when anything on <span class="inline-block rounded p-1 font-bold">Peer</span> is hovered/pressed</div> </div> <div class="grid gap-4 rounded-md border bg-white p-4 pressed-action:text-white"> <div class="group/card peer/card grid gap-2 rounded-md border p-4 hovered-action:bg-blue-100 pressed-action:bg-blue-500"> <h2 class="font-bold">group/card - peer/card - group-hovered-action/card</h2> <button type="button" class="action block rounded border bg-amber-200 px-2 py-1 hovered:bg-amber-400">Action</button> <div class="rounded bg-teal-100 p-2 group-hovered-action/card:bg-teal-500 group-pressed-action/card:bg-teal-800">I change state when <span class="inline-block rounded p-1 font-bold">Group > Action</span> is hovered/pressed</div> <div class="rounded bg-red-500 p-2 text-white">I don't change</div> </div> <div class="rounded bg-green-100 p-2 peer-hovered-action/card:bg-green-500 peer-pressed-action/card:bg-green-800">I change state whn <span class="inline-block rounded p-1 font-bold">Peer > Action</span> is hovered/pressed</div> <div class="rounded bg-indigo-100 p-2 peer-hovered/card:bg-indigo-500 peer-pressed/card:bg-indigo-800">I change states when anything on <span class="inline-block rounded p-1 font-bold">Peer</span> is hovered/pressed</div> </div> <div class="grid gap-4 rounded-md border bg-white p-4 pressed-[.selector]:text-white"> <div class="group peer grid gap-2 rounded-md border p-4 hovered-[.selector]:bg-blue-100 pressed-[.selector]:bg-blue-500"> <h2 class="font-bold">group - peer - group-hovered-[.selector]</h2> <button type="button" class="selector block rounded border bg-amber-200 px-2 py-1 hovered:bg-amber-400">Action</button> <div class="rounded bg-teal-100 p-2 group-hovered-[.selector]:bg-teal-500 group-pressed-[.selector]:bg-teal-800">I change state when <span class="inline-block rounded p-1 font-bold">Group > Action</span> is hovered/pressed</div> <div class="rounded bg-red-500 p-2 text-white">I don't change</div> </div> <div class="rounded bg-green-100 p-2 peer-hovered-[.selector]:bg-green-500 peer-pressed-[.selector]:bg-green-800">I change state whn <span class="inline-block rounded p-1 font-bold">Peer > Action</span> is hovered/pressed</div> <div class="rounded bg-indigo-100 p-2 peer-hovered:bg-indigo-500 peer-pressed:bg-indigo-800">I change states when anything on <span class="inline-block rounded p-1 font-bold">Peer</span> is hovered/pressed</div> </div> <div class="grid gap-4 rounded-md border bg-white p-4 pressed-[.selector]:text-white"> <div class="group/card peer/card grid gap-2 rounded-md border p-4 hovered-[.selector]:bg-blue-100 pressed-[.selector]:bg-blue-500"> <h2 class="font-bold">group/card - peer/card - group-hovered-[.selector]/card</h2> <button type="button" class="selector block rounded border bg-amber-200 px-2 py-1 hovered:bg-amber-400">Action</button> <div class="rounded bg-teal-100 p-2 group-hovered-[.selector]/card:bg-teal-500 group-pressed-[.selector]/card:bg-teal-800">I change state when <span class="inline-block rounded p-1 font-bold">Group > Action</span> is hovered/pressed</div> <div class="rounded bg-red-500 p-2 text-white">I don't change</div> </div> <div class="rounded bg-green-100 p-2 peer-hovered-[.selector]/card:bg-green-500 peer-pressed-[.selector]/card:bg-green-800">I change state whn <span class="inline-block rounded p-1 font-bold">Peer > Action</span> is hovered/pressed</div> <div class="rounded bg-indigo-100 p-2 peer-hovered/card:bg-indigo-500 peer-pressed/card:bg-indigo-800">I change states when anything on <span class="inline-block rounded p-1 font-bold">Peer</span> is hovered/pressed</div> </div> </div> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,74 @@ import plugin from "tailwindcss/plugin"; type MaybeString = string | null | undefined; type UtilType = "group" | "peer"; // Main plugin export default plugin(function ({ matchVariant, theme, e }) { const values = { values: { DEFAULT: "", ...theme("betterStates", {}), }, }; const selector = (state: string, prefix: MaybeString) => { return `${prefix ?? "&"}:${state}:not(:disabled)`; }; const maybeWrap = (state: string, prefix = "&", value: MaybeString) => { return ["", null, undefined].includes(value) ? selector(state, prefix) : `${prefix}:has(${selector(state, value)})`; }; const merge = ( type: UtilType, value: MaybeString, modifier: string | null, callback: (value: MaybeString, prefix: string) => string[] ) => { const typeClass = modifier ? `.${type}\\/${e(modifier)}` : `.${type}`; const append = type === "group" ? " &" : " ~ &"; return callback(value, `:merge(${typeClass})`).map((x) => `${x}${append}`); }; // Hovered ________________________________________________________________________ const hovered = (value: MaybeString = null, prefix = "&") => [ maybeWrap("focus-visible", prefix, value), maybeWrap("hover", prefix, value), maybeWrap("has(:focus-visible)", prefix, value), ]; matchVariant("hovered", (value) => hovered(value), values); matchVariant( "group-hovered", (value, { modifier }) => merge("group", value, modifier, hovered), values ); matchVariant( "peer-hovered", (value, { modifier }) => merge("peer", value, modifier, hovered), values ); // Pressed ________________________________________________________________________ const pressed = (value: MaybeString = null, prefix = "&") => [ maybeWrap("active", prefix, value), maybeWrap("has(:active)", prefix, value), `@media (hover: none) { ${maybeWrap("hover", prefix, value)} }`, `@media (hover: none) { ${maybeWrap("focus", prefix, value)} }`, ]; matchVariant("pressed", (value) => pressed(value), values); matchVariant( "group-pressed", (value, { modifier }) => merge("group", value, modifier, pressed), values ); matchVariant( "peer-pressed", (value, { modifier }) => merge("peer", value, modifier, pressed), values ); }); This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,77 @@ const plugin = require('tailwindcss/plugin') /** @type {import('tailwindcss').Config} */ export default { theme: { extend: { betterStates: { action: '.action', }, }, }, plugins: [ plugin(function ({ matchVariant, theme, e }) { const values = { values: { DEFAULT: '', ...theme('betterStates', {}), }, } const selector = (state, prefix) => { return `${prefix ?? '&'}:${state}:not(:disabled)` } const maybeWrap = (state, prefix = '&', value) => { return ['', null, undefined].includes(value) ? selector(state, prefix) : `${prefix}:has(${selector(state, value)})` } const merge = (type, value, modifier, callback) => { const typeClass = modifier ? `.${type}\\/${e(modifier)}` : `.${type}` const append = type === 'group' ? ' &' : ' ~ &' return callback(value, `:merge(${typeClass})`).map((x) => `${x}${append}`) } // Hovered ________________________________________________________________________ const hovered = (value = null, prefix = '&') => [ maybeWrap('focus-visible', prefix, value), maybeWrap('hover', prefix, value), maybeWrap('has(:focus-visible)', prefix, value), ] matchVariant('hovered', (value) => hovered(value), values) matchVariant( 'group-hovered', (value, { modifier }) => merge('group', value, modifier, hovered), values, ) matchVariant( 'peer-hovered', (value, { modifier }) => merge('peer', value, modifier, hovered), values, ) // Pressed ________________________________________________________________________ const pressed = (value = null, prefix = '&') => [ maybeWrap('active', prefix, value), maybeWrap('has(:active)', prefix, value), `@media (hover: none) { ${maybeWrap('hover', prefix, value)} }`, `@media (hover: none) { ${maybeWrap('focus', prefix, value)} }`, ] matchVariant('pressed', (value) => pressed(value), values) matchVariant( 'group-pressed', (value, { modifier }) => merge('group', value, modifier, pressed), values, ) matchVariant( 'peer-pressed', (value, { modifier }) => merge('peer', value, modifier, pressed), values, ) }), ], } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,12 @@ /** @type {import('tailwindcss').Config} */ export const theme = { extend: { betterStates: { action: ".action", }, }; export const plugins = [ require("./plugin-better-state"), ];