Skip to content

Instantly share code, notes, and snippets.

@JWPapi
Created February 11, 2026 14:26
Show Gist options
  • Select an option

  • Save JWPapi/9cf47f55671eaea3107b6d20ecd6e940 to your computer and use it in GitHub Desktop.

Select an option

Save JWPapi/9cf47f55671eaea3107b6d20ecd6e940 to your computer and use it in GitHub Desktop.
ESLint rule: no-hover-translate - Disallows hover translate effects in Tailwind CSS (causes chase UX bug)
/**
* ESLint rule: no-hover-translate
*
* Disallows hover translate effects in Tailwind CSS classes.
* These create a "chase" effect where elements move away from the cursor,
* especially when approaching from below.
*
* Bad: hover:-translate-y-1, hover:translate-y-2, group-hover:-translate-y-0.5
* Good: hover:shadow-md, hover:scale-105 (if you need hover feedback)
*/
export default {
rules: {
'no-hover-translate': {
meta: {
type: 'problem',
docs: {
description: 'Disallow hover translate effects which cause chase/escape UX issues'
},
messages: {
noHoverTranslate:
"Don't use '{{ className }}' — hover translate creates a chase effect where elements move away from the cursor. Use hover:shadow-* instead for hover feedback."
},
schema: []
},
create(context) {
const hoverTranslatePattern = /\b(group-)?hover:-?translate-[xy]-/g
function checkString(value, node) {
const matches = value.match(hoverTranslatePattern)
if (matches) {
for (const match of matches) {
context.report({
node,
messageId: 'noHoverTranslate',
data: { className: match.replace(/-$/, '-*') }
})
}
}
}
function checkNode(node) {
if (!node) return
if (node.type === 'Literal' && typeof node.value === 'string') {
checkString(node.value, node)
} else if (node.type === 'TemplateLiteral') {
for (const quasi of node.quasis) {
checkString(quasi.value.raw, node)
}
} else if (node.type === 'ConditionalExpression') {
checkNode(node.consequent)
checkNode(node.alternate)
} else if (node.type === 'BinaryExpression' && node.operator === '+') {
checkNode(node.left)
checkNode(node.right)
}
}
return {
JSXAttribute(node) {
if (
node.name &&
node.name.name === 'className' &&
node.value
) {
if (node.value.type === 'Literal') {
checkString(node.value.value, node.value)
} else if (node.value.type === 'JSXExpressionContainer') {
checkNode(node.value.expression)
}
}
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment