Appearance
Fix: Wrap hover styles in @media (hover: hover) for mobile
Problem
None of our interactive components guard hover styles behind @media (hover: hover). This causes "sticky hover" on touch devices - elements stay in their hover state after a tap until the user taps elsewhere.
Solution
Wrap visual hover effects in @media (hover: hover) media queries. This ensures hover styles only apply on devices with a true hover capability (mouse/trackpad), not on touch devices.
Note: cursor: pointer does NOT need wrapping - it has no visual side-effect on touch devices.
Files to change
1. src/components/blocks/basic/Choice.vue (~line 225)
Current:
js
'&:hover': {
transform: 'scale3d(1.05,1.05,1)',
filter: 'brightness(1.1) saturate(1.05)',
},kImpact: High - scale + brightness is very visible as sticky hover after tap.
2. src/components/creative/CreativeFormBlock/CreativeFormBlock.vue
Multiple hover declarations:
a) Input fields (~line 511)
js
[`${this.toClassSelector(inputField)}:hover`]: pseudoClassStyles.hover,b) Dropdown (~line 646)
js
'.dropdown-input-container select:hover': pseudoClassStyles.hover,c) Stars/emoji rating (~line 828)
js
'.star:hover, .star.filled, .emoji:hover': {
transform: 'scale(1.1)',
},Note: .star.filled is not a hover state - keep it outside the media query.
d) Clickout link (~line 858)
js
'.clickout-link:hover': {
textDecoration: 'none',
},Low impact, but should be wrapped for consistency.
e) Range/checkbox/radio hover reset (~line 881)
js
[this.toMultiClassSelector(rangeField, checkboxField, radioField, countryCode, ':hover')]: {
boxShadow: 'none !important',
},This removes hover effects - still wrap it since it's only relevant when hover exists.
f) Submit button (~line 949)
js
[`${this.toClassSelector(submitButton)}:hover`]: submitBlock?.hoverStyle ? { ... } : {},Impact: High - user-configured hover style stays stuck after tap.
3. src/components/creative/CreativeSliderBlock/CreativeSliderBlock.vue (~lines 637, 652)
js
[`&:hover ${this.toClassSelector(arrowInner)}`]: {
transform: `translate3d(-3px, 0, 0)`, // left arrow
},
[`&:hover ${this.toClassSelector(arrowInner)}`]: {
transform: `translate3d(3px, 0, 0)`, // right arrow
},4. src/components/blocks/rich/Slider.vue (~lines 429, 469)
js
'&:hover svg': {
transform: 'translate3d(1px, 0, 0)',
transition: 'transform 100ms',
},
'&:hover': {
'& .slider_arrow_path_outer': { ... },
'&.slider_arrow_path_inner': { ... },
},5. src/components/blocks/rich/Slider/SliderSlide.vue (~lines 341, 364)
js
'&:hover': {
'color': 'rgba(0,0,0,.9) !important',
'& .encircled-arrow-background': { ... },
'& .encircled-arrow-glow': { ... },
},
'&:hover': {
'backgroundColor': 'white',
'& .slider-custom-text:before': { ... },
},Note: This component already partially handles mobile with :not(.mobile) on the second hover block. Verify that the .mobile class approach can be replaced/complemented with @media (hover: hover).
6. src/components/creative/CreativeVideoBlock/CreativeVideoControls.vue (~line 90)
js
'&:hover': {
opacity: 1,
transition: 'none',
},Impact: Medium - video control icon stays fully opaque after tap.
7. src/components/creative/VisualElements/CreativeButtonBlock.vue (~line 101)
js
'&:hover': {
cursor: 'pointer',
},Skip this one - cursor: pointer has no visual effect on touch devices.
Implementation approach
Since all styles are CSS-in-JS, we can't use @media directly in the object. Two options:
Option A: Use a helper function (recommended)
Create a utility that conditionally includes hover styles based on a CSS media query wrapper:
js
// e.g. in src/utils/styleHelpers.js
export function hoverStyle(selector, styles) {
return {
[`@media (hover: hover)`]: {
[selector]: styles,
},
}
}Check if the CSS-in-JS library used (likely JSS or similar) supports @media nesting in objects.
Option B: Use matchMedia in JS
Check window.matchMedia('(hover: hover)').matches and conditionally include hover styles in the computed style objects. Simpler but less elegant - the check runs once and doesn't update if device capabilities change (e.g. connecting a mouse to a tablet).
Before implementing
- Verify which CSS-in-JS approach the project uses and whether
@medianesting is supported in style objects - Check if
SliderSlide.vue's.mobileclass can be fully replaced by the media query approach - Test on actual mobile devices after changes