<marquee> elements are deprecated and must not be used
Scrolling <marquee> text keeps moving and cannot be paused, so customers with reading difficulties, attention disorders or low vision cannot read the offer or shipping message it contains. The movement also distracts from the products and the checkout.
- Rule id
- marquee
- Source
- axe-core 4.13.0
- Impact
- Serious
- Fix tier
- AUTO
- on Shopify, Shopware, WooCommerce and Magento
- Status
- runs
What the check does
Ensure <marquee> elements are not used
The rule selects every <marquee> element on the page, hidden ones included, and fails each one that is visible on screen. It does not read the element’s attributes or its text, so the speed, the direction and whether the text actually moves play no part.
A <marquee> passes only when axe-core finds it hidden from view, for example when it or an ancestor has display: none or opacity: 0, when it has visibility: hidden, when clip-path: inset(50%) or a zero-size clip hides it, or when it is positioned above the top of the page. One further down a long page still counts as visible.
Validiscan’s scanner loads each page at 1440 by 900 pixels (desktop) and 390 by 844 (mobile) by default, so a <marquee> the theme hides at one size is reported only for the other. Moving text built with CSS animation or JavaScript is outside this rule.
How to fix it
Remove the <marquee> element and show its text as static content. If a moving announcement bar is wanted, build it with CSS that respects prefers-reduced-motion and give it a visible pause control.
WCAG success criteria
- 2.2.2 Pause, Stop, Hide, level A
EN 301 549 clauses
| WCAG criterion | Version 3.2.1 | Version 4.1.1 |
|---|---|---|
| 2.2.2 | 9.2.2.2 | 9.2.2.2 |
Where a recipe fixes it
Shopify: recipe remove-blink-marquee
Show the before and after (1)
sections/announcement-bar.liquid Changes to sections/announcement-bar.liquid: 2 added, 2 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 3 | 3 | -%} | |
| 4 | 4 | <div class="announcement-bar color-accent"{% if bar_lang != '' %} lang="{{ bar_lang }}"{% endif %}> | |
| 5 | 5 | <div class="page-width"> | |
| 6 | Removed | <marquee class="announcement-bar__message" behavior="scroll" direction="left" scrollamount="4">{{ section.settings.text | escape }}</marquee> | |
| 7 | Removed | <p class="announcement-bar__note"><blink class="announcement-bar__flag">New</blink> winter collection</p> | |
| 6 | Added | <div class="announcement-bar__message">{{ section.settings.text | escape }}</div> | |
| 7 | Added | <p class="announcement-bar__note"><span class="announcement-bar__flag">New</span> winter collection</p> | |
| 8 | 8 | </div> | |
| 9 | 9 | </div> | |
| 10 | 10 |
Shopware: recipe remove-blink-marquee
Show the before and after (1)
Resources/views/storefront/layout/header/top-bar.html.twig Changes to Resources/views/storefront/layout/header/top-bar.html.twig: 2 added, 2 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 4 | 4 | {% block layout_header_top_bar_usp %} | |
| 5 | 5 | <p class="top-bar-usp"> | |
| 6 | 6 | {% sw_icon 'checkmark' style { size: 'xs' } %} | |
| 7 | Removed | <marquee class="top-bar-marquee" behavior="scroll" scrollamount="4"><span>{{ 'header.topBarUsp'|trans }}</span></marquee> | |
| 7 | Added | <span class="top-bar-marquee"><span>{{ 'header.topBarUsp'|trans }}</span></span> | |
| 8 | 8 | </p> | |
| 9 | 9 | {% endblock %} | |
| 10 | 10 | ||
| 13 | 13 | <li class="top-bar-nav-item"> | |
| 14 | 14 | <a class="top-bar-nav-link top-bar-help" href="{{ url('/contact') }}"> | |
| 15 | 15 | {% sw_icon 'headset' style { size: 'xs' } %} | |
| 16 | Removed | <blink class="top-bar-blink">{{ 'header.topBarHelp'|trans }}</blink> | |
| 16 | Added | <span class="top-bar-blink">{{ 'header.topBarHelp'|trans }}</span> | |
| 17 | 17 | </a> | |
| 18 | 18 | </li> | |
| 19 | 19 | </ul> |
WooCommerce: recipe remove-blink-marquee
Show the before and after (2)
footer.php Changes to footer.php: 1 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 31 | 31 | <?php endfor; ?> | |
| 32 | 32 | </div> | |
| 33 | 33 | ||
| 34 | Removed | <marquee class="kb-ticker" behavior="scroll" scrollamount="3"><?php esc_html_e( 'Free returns within 30 days', 'kestrel-birch' ); ?></marquee> | |
| 34 | Added | <div class="kb-ticker"><?php esc_html_e( 'Free returns within 30 days', 'kestrel-birch' ); ?></div> | |
| 35 | 35 | <div class="site-info"> | |
| 36 | 36 | <?php // VALIDISCAN-SEED color-contrast: .site-info and the footer copy are #888 on #f4f1ec (3.1:1), see main.css. ?> | |
| 37 | 37 | <p> |
functions.php Changes to functions.php: 1 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 223 | 223 | */ | |
| 224 | 224 | function kb_single_product_usp() { | |
| 225 | 225 | // VALIDISCAN-SEED vs-reflow: .kb-usp is white-space: nowrap in assets/css/woocommerce.css. | |
| 226 | Removed | echo '<p class="kb-usp"><blink class="kb-new">' . esc_html__( 'New', 'kestrel-birch' ) . '</blink> ' . esc_html__( 'Same-day dispatch · Free returns · Climate-neutral delivery', 'kestrel-birch' ) . '</p>'; | |
| 226 | Added | echo '<p class="kb-usp"><span class="kb-new">' . esc_html__( 'New', 'kestrel-birch' ) . '</span> ' . esc_html__( 'Same-day dispatch · Free returns · Climate-neutral delivery', 'kestrel-birch' ) . '</p>'; | |
| 227 | 227 | } | |
| 228 | 228 | add_action( 'woocommerce_single_product_summary', 'kb_single_product_usp', 31 ); | |
| 229 | 229 |
functions.php Changes to functions.php: 1 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 11 | 11 | add_action( 'after_setup_theme', 'minimal_setup' ); | |
| 12 | 12 | ||
| 13 | 13 | function minimal_sale() { | |
| 14 | Removed | echo '<p><blink class="sale">' . esc_html__( 'Sale', 'minimal' ) . '</blink></p>'; | |
| 14 | Added | echo '<p><span class="sale">' . esc_html__( 'Sale', 'minimal' ) . '</span></p>'; | |
| 15 | 15 | } |
template-parts/notice.php Changes to template-parts/notice.php: 3 added, 3 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 1 | 1 | <div class="notice"> | |
| 2 | Removed | <marquee class="notice__ticker" behavior="scroll" direction="left" scrollamount="4"> | |
| 2 | Added | <div class="notice__ticker"> | |
| 3 | 3 | <?php esc_html_e( 'Free shipping this week', 'minimal' ); ?> | |
| 4 | Removed | </marquee> | |
| 5 | Removed | <blink><?php esc_html_e( 'New!', 'minimal' ); ?></blink> | |
| 4 | Added | </div> | |
| 5 | Added | <span><?php esc_html_e( 'New!', 'minimal' ); ?></span> | |
| 6 | 6 | </div> |
Magento: recipe remove-blink-marquee
Show the before and after (1)
app/design/frontend/Validiscan/fixture/Magento_Cms/content/home.html Changes to app/design/frontend/Validiscan/fixture/Magento_Cms/content/home.html: 2 added, 2 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 1 | Removed | <marquee class="news-ticker" behavior="scroll" direction="left" scrollamount="4"> | |
| 1 | Added | <div class="news-ticker"> | |
| 2 | 2 | <p>Autumn sale: 20% off all jackets</p> | |
| 3 | Removed | </marquee> | |
| 3 | Added | </div> | |
| 4 | 4 | <!-- VALIDISCAN-SEEDED vs-carousel + vs-motion + aria-hidden-focus: Page Builder slider autoplays (data-autoplay and a CSS animation) with no pause control; hidden slides keep focusable links; dots have no names --> | |
| 5 | 5 | <div | |
| 6 | 6 | class="pagebuilder-slider slick-slider" |
app/design/frontend/Validiscan/fixture/Magento_Theme/templates/html/top-promo.phtml Changes to app/design/frontend/Validiscan/fixture/Magento_Theme/templates/html/top-promo.phtml: 1 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 5 | 5 | // Fixed variant: moved into the header (header.container) and styled with line-height 1.5, no fixed height. | |
| 6 | 6 | ?> | |
| 7 | 7 | <!-- VALIDISCAN-SEEDED region + vs-text-spacing: the promo text sits outside every landmark in a fixed 26px-high box --> | |
| 8 | Removed | <div class="top-promo"><p class="top-promo-text"><blink><?= $block->escapeHtml(__('Free shipping on orders over €75')) ?></blink><br><?= $block->escapeHtml(__('Free 30-day returns on every jacket')) ?></p></div> | |
| 8 | Added | <div class="top-promo"><p class="top-promo-text"><span><?= $block->escapeHtml(__('Free shipping on orders over €75')) ?></span><br><?= $block->escapeHtml(__('Free 30-day returns on every jacket')) ?></p></div> |
Start with a free check of your shop.
Send your shop’s address. Validiscan scans it and replies with every failure the scan can prove, with the page and the code.
Request a free checkOr write to info@validiscan.com.
What the free check covers, and what it leaves out- You send
- your shop’s address
- Pages scanned
- up to 15
- You get
- every failure the scan can prove, with the page and the code
- On your shop
- public pages only; no login, nothing bought, no forms sent
- What it is not
- a full audit