The European Accessibility Act (EAA) requires web shops to be accessible to all from June 28, 2025

Open to all! Ask other shopowners for help.
User avatar
martindavinci
Member
Posts: 38
Joined: Mon Mar 08, 2021 8:33 am
Phoenix Version: 1.1.0.5
Has thanked: 2 times

The European Accessibility Act (EAA) requires web shops to be accessible to all from June 28, 2025

Post by martindavinci »

The other day I read an article supplied by the Dutch Chamber of Commerce about the following:

The European Accessibility Act (EAA) requires Web shops to be accessible to everyone, including people with disabilities, starting June 28, 2025. This means that Web shops must comply with the Web Content Accessibility Guidelines (WCAG) version 2.1, level AA. The EAA sets a minimum standard for accessibility, which means that web shops must be accessible and usable not only for people with disabilities, but also for a wider audience.
What does this mean for web shops?

WCAG 2.1 AA:
Web shops have to comply with the WCAG 2.1 AA guidelines, which means that, among other things, they have to meet the principles of accessibility: perceivable, operable, understandable and robust.

Accessibility for all users:
The web shop must be accessible to everyone, regardless of their disability or needs.
Adaptations:
Web shops must be adapted to meet accessibility requirements, for example by providing text alternatives for images, using subtitles and audio descriptions for videos, and providing keyboard navigation.
Monitoring and supervision:
The Consumer and Market Authority (ACM) will monitor compliance with the EAA.
Inclusive experience:
The intention is to create an inclusive user experience for everyone.

Why is this important?

Equity:
It is important to give people with disabilities the same access to online stores as people without disabilities.
Business benefits:
Accessible Web shops are also better for business, for example by reaching a larger market and building a more positive reputation.
Legal obligation:
The EAA is a legal obligation, so Web shops must comply with it.

Conclusion:
The European Accessibility Act has a major impact on Web shops in Europe. As of June 28, 2025, Web shops are required to comply with the WCAG 2.1 AA guidelines and make their services accessible to all. This will require adjustments to the webshop, but will also benefit businesses and provide an inclusive experience for all consumers.


I wonder if this will awaken in the Phoenix cart community the call to do something with it.
It has, in my opinion - at a later stage and time - a similar impact as the GDPR. Of course, I am aware that there will be no binding rules and sanctions at this point, and that there will be a bottom limit of 2 million euros of sales. Smaller webshops will be less affected, if at all, by the regulations. Yet I read, as soon as you delve a little deeper, that the search engines will also take this into account. I also think that by tightening relations with the UK and being critical of US tech companies, dirtt will play a bigger role. I am very curious to see if this resonates in our community.


Join The Code Co-op to get access to your library in the Code Co-op Forum
User avatar
burt
Core Team
Posts: 4546
Joined: Tue Oct 29, 2019 9:37 am
Phoenix Version: v1.1.0.8
Has thanked: 252 times
Been thanked: 412 times

Re: The European Accessibility Act (EAA) requires web shops to be accessible to all from June 28, 2025

Post by burt »

We've worked really hard from v1.0.9.9 on accessibility.
We tried to get as far as we could, but we had quite little feedback on it, unfortunately.

A good plan would be for anyone interested to install the latest release and run it through some online automatic checkers and see if any notices popup, that require some changes.

I am more than happy to react (in the core code) to anyone who can post in this thread, something about Accessibility that needs fixing.

Accessibility is super-important in my opinion. And I do believe that Google etc will, at some point, flag on accessibility, just as they did back in the day on responsive sites.
I am not here to build for you.
I am here to build with you. Let's help each other.
artfulweb
Contributor
Posts: 184
Joined: Thu Oct 29, 2020 12:34 pm
Phoenix Version: v1.1.0.6
Has thanked: 29 times
Been thanked: 14 times

Re: The European Accessibility Act (EAA) requires web shops to be accessible to all from June 28, 2025

Post by artfulweb »

Hello Burt and thank you for addressing this!

I am currently using a tool called Accessibility Insights, an extension for Edge and Chrome, on my desktop and I must admit it is awesome as it allows to find EVERYTHING that is covered in the EEA. You and the developpers would better use it than I as my knowledge is limited. I am posting some simple results as I have so much more to do. Below are some results from the index page and a category page on Phoenix 1.1.0.1 :
===================
(1)Title: WCAG 4.1.2: Buttons must have discernible text (button[type="submit"])
Tags: Accessibility, WCAG 4.1.2, button-name

Issue: Buttons must have discernible text (button-name - https://accessibilityinsights.io/info-e ... utton-name)

Target application: Welcome to Our Online Store | - https://www.mysite.com/index.php

Element path: button[type="submit"]

Snippet: <button type="submit" class="btn btn-info"><i class="fas fa-search"></i></button>

Environment: Microsoft Edge version 136.0.0.0
How to fix
Fix ONE of the following:
Element does not have inner text that is visible to screen readers
aria-label attribute does not exist or is empty
aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty
Element has no title attribute
Element does not have an implicit (wrapped) <label>
Element does not have an explicit <label>
Element's default semantics were not overridden with role="none" or role="presentation"
====

(2)Title: WCAG 1.4.3: Elements must meet minimum color contrast ratio thresholds (#open_preferences_center)
Tags: Accessibility, WCAG 1.4.3, color-contrast

Issue: Elements must meet minimum color contrast ratio thresholds (color-contrast - https://accessibilityinsights.io/info-e ... r-contrast)

Target application: Welcome to Our Online Store | Aux Belles Laines - https://www.auxbelleslaines.com/index.php

Element path: #open_preferences_center

Snippet: <a href="#" id="open_preferences_center"> PREFERENCES COOKIES</a>

Environment: Microsoft Edge version 136.0.0.0

How to fix (THIS ONE I FIXED, but given as an example)
Fix the following:
Element has insufficient color contrast of 3.79 (foreground color: #0d6efd, background color: #e9ecef, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1Element has insufficient color contrast of 3.79 (foreground color: #0d6efd, background color: #e9ecef, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1
Use foreground color: #0b62e4 and the original background color: #e9ecef to meet a contrast ratio of 4.56:1Use foreground color: #0b62e4 and the original background color: #e9ecef to meet a contrast ratio of 4.56:1
Or use background color: #ffffff and the original foreground color: #0d6efd to meet a contrast ratio of 4.50:1.
====

(3)Title: ,WCAG 1.3.1,WCAG 4.1.2,Best Practice: Ensure role attribute has an appropriate value for the element (.ms-3)
Tags: Accessibility, , WCAG 1.3.1, WCAG 4.1.2, Best Practice, aria-allowed-role

Issue: Ensure role attribute has an appropriate value for the element (aria-allowed-role - https://dequeuniversity.com/rules/axe/4 ... ion=msftAI)

Target application: Lang Yarns Bliss Yarn | Aux Belles Laines - https://www.auxbelleslaines.com/lang-ya ... 1_965.html

Element path: .ms-3

Snippet: <li class="btn-group btn-group ms-3" role="group">

How to fix:
Fix any of the following:
ARIA role group is not allowed for given element

Environment: Microsoft Edge version 136.0.0.0
=====

(4)Title: WCAG 1.3.1: Ensure that lists are structured correctly (.gap-2)
Tags: Accessibility, WCAG 1.3.1, list

Issue: Ensure that lists are structured correctly (list - https://accessibilityinsights.io/info-examples/web/list)

Target application: Lang Yarns Bliss Yarn | - https://www.mysitecom/lang-yarns-bliss- ... 1_965.html

Element path: .gap-2

Snippet: <ul class="nav d-flex gap-2">

Related paths:
.gap-2 > .nav

How to fix:
Fix all of the following:
List element has direct children that are not allowed: ul
======
User avatar
burt
Core Team
Posts: 4546
Joined: Tue Oct 29, 2019 9:37 am
Phoenix Version: v1.1.0.8
Has thanked: 252 times
Been thanked: 412 times

Re: The European Accessibility Act (EAA) requires web shops to be accessible to all from June 28, 2025

Post by burt »

It's difficult for me as I don't have time to helping individual shops to fix things as some might shopowners might be on 1.0.4.5, some on 1.0.7.18, some on 1.1.0.0 and so on. It would be opening Pandoras Box. I am sorry.

I am able to work on fixing accessibility issues in the latest release (currently 1.1.0.2), should anyone find any.
I am not here to build for you.
I am here to build with you. Let's help each other.
artfulweb
Contributor
Posts: 184
Joined: Thu Oct 29, 2020 12:34 pm
Phoenix Version: v1.1.0.6
Has thanked: 29 times
Been thanked: 14 times

Re: The European Accessibility Act (EAA) requires web shops to be accessible to all from June 28, 2025

Post by artfulweb »

Me Again Burt. Updates to 1.1.0.2 and there are a couple issues :

1 create_account.php
Path
#inputEmail
Snippet
<input name="email_address" type="email" id="inputEmail" autocomplete="username email" placeholder="" minlength="6" required="" aria-required="true" class="form-control">

Fix the following:
the autocomplete attribute is incorrectly formatted

2 Page Links on bottom of page
Path
.page-link[href="#"]
Snippet
<a class="page-link" href="#" tabindex="-1"><i class="fas fa-angle-left"></i></a>

Fix ONE of the following:
Element does not have text that is visible to screen readers
aria-label attribute does not exist or is empty
aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty
Element has no title attribute
=====

Also very important for everyone to note that Cookie Consent Banners fail the accessibility tests, including the nice one you uploaded as an addon and we all do need them so that is a huge issue that will cause the stores to be fined as well.

I will update to 1.1.0.3 that you just uploaded to check as well.
User avatar
burt
Core Team
Posts: 4546
Joined: Tue Oct 29, 2019 9:37 am
Phoenix Version: v1.1.0.8
Has thanked: 252 times
Been thanked: 412 times

Re: The European Accessibility Act (EAA) requires web shops to be accessible to all from June 28, 2025

Post by burt »

artfulweb wrote: Sat May 31, 2025 9:23 am <input name="email_address" type="email" id="inputEmail" autocomplete="username email" placeholder="" minlength="6" required="" aria-required="true" class="form-control">

Fix the following:
the autocomplete attribute is incorrectly formatted
I'm not sure which would be best to use here so I asked chatGPT;
If you're autofilling a username eg to sign in, use:
autocomplete="username"

If you're autofilling an email address in a non-login context eg creating an account, use:
autocomplete="email"
I am not here to build for you.
I am here to build with you. Let's help each other.
User avatar
burt
Core Team
Posts: 4546
Joined: Tue Oct 29, 2019 9:37 am
Phoenix Version: v1.1.0.8
Has thanked: 252 times
Been thanked: 412 times

Re: The European Accessibility Act (EAA) requires web shops to be accessible to all from June 28, 2025

Post by burt »

artfulweb wrote: Sat May 31, 2025 9:23 am 2 Page Links on bottom of page
Path
.page-link[href="#"]
Snippet
<a class="page-link" href="#" tabindex="-1"><i class="fas fa-angle-left"></i></a>
Add new file:

/includes/system/versioned/1.01.00.04/split_page_results.php

New file content:

Code: Select all

<?php
/*
  $Id$

  CE Phoenix, E-Commerce made Easy
  https://phoenixcart.org

  Copyright (c) 2021 Phoenix Cart

  Released under the GNU General Public License
*/

  class splitPageResults {

    public $sql_query;
    public $number_of_rows;

    protected $current_page_number;
    protected $number_of_pages;
    protected $number_of_rows_per_page;
    protected $page_name;

    public function __construct($query, $max_rows, $count_key = '*', $page_holder = 'page') {
      $this->sql_query = $query;
      $this->page_name = $page_holder;

      $page = $_GET[$this->page_name] ?? $_POST[$this->page_name] ?? '';

      if (empty($page) || !is_numeric($page)) {
        $page = 1;
      }
      $this->current_page_number = $page;

      $this->number_of_rows_per_page = $max_rows;

      $pos_to = strlen($this->sql_query);
      $pos_from = stripos($this->sql_query, ' FROM');
      $pos_where = strripos($this->sql_query, ' WHERE') ?: $pos_from;

      $pos_group_by = stripos($this->sql_query, ' GROUP BY', $pos_where);
      if ($pos_group_by && ($pos_group_by < $pos_to)) {
        $pos_to = $pos_group_by;
      }

      $pos_having = stripos($this->sql_query, ' HAVING', $pos_where);
      if ($pos_having && ($pos_having < $pos_to)) {
        $pos_to = $pos_having;
      }

      $pos_order_by = stripos($this->sql_query, ' ORDER BY', $pos_where);
      if ($pos_order_by && ($pos_order_by < $pos_to)) {
        $pos_to = $pos_order_by;
      }

      if (stripos($this->sql_query, 'DISTINCT') || stripos($this->sql_query, 'GROUP BY', $pos_where)) {
        $count_string = 'DISTINCT ' . $GLOBALS['db']->escape($count_key);
      } else {
        $count_string = $GLOBALS['db']->escape($count_key);
      }

      $count_query = $GLOBALS['db']->query("SELECT COUNT(" . $count_string . ") AS total " . substr($this->sql_query, $pos_from, ($pos_to - $pos_from)));
      $count = $count_query->fetch_assoc();

      $this->number_of_rows = $count['total'];

      $this->number_of_pages = ceil($this->number_of_rows / $this->number_of_rows_per_page);

      if ($this->current_page_number > $this->number_of_pages) {
        $this->current_page_number = $this->number_of_pages;
      }

      $offset = ($this->number_of_rows_per_page * ($this->current_page_number - 1));

      $this->sql_query .= " LIMIT " . max($offset, 0) . ", " . $this->number_of_rows_per_page;
    }

    public function display_links($max_page_links, $link = null) {
      if (is_null($link)) {
        $link = $GLOBALS['Linker']->build()->retain_query_except(['page', 'info']);
      } else if (is_string($link)) {
        $link = $GLOBALS['Linker']->build(null, phoenix_parameterize(rtrim($link, '&')));
      }

      $display_links_string = '<nav aria-label="...">';
        $display_links_string .= '<ul class="pagination pagination-lg justify-content-end">';

// previous button - not displayed on first page
        if ($this->current_page_number > 1) {
          $display_links_string .= '<li class="page-item">';
            $display_links_string .= '<a class="page-link" href="' . $link->set_parameter($this->page_name, $this->current_page_number - 1) . '" title=" ' . PREVNEXT_TITLE_PREVIOUS_PAGE . ' "><i class="fas fa-angle-left"></i></a>';
          $display_links_string .= '</li>';
        } else {
          $display_links_string .= '<li class="page-item disabled" aria-disabled="true">';
            $display_links_string .= '<span class="page-link"><i class="fas fa-angle-left" aria-hidden="true"></i></span>';
          $display_links_string .= '</li>';
        }

// check if number_of_pages > $max_page_links
        $cur_window_num = (int)($this->current_page_number / $max_page_links);
        if ($this->current_page_number % $max_page_links) {
          $cur_window_num++;
        }

        $max_window_num = (int)($this->number_of_pages / $max_page_links);
        if ($this->number_of_pages % $max_page_links) {
          $max_window_num++;
        }

// previous window of pages
        if ($cur_window_num > 1) {
          $display_links_string .= '<li class="page-item">';
            $display_links_string .= '<a class="page-link" href="' . $link->set_parameter($this->page_name, ($cur_window_num - 1) * $max_page_links) . '" title=" ' . sprintf(PREVNEXT_TITLE_PREV_SET_OF_NO_PAGE, $max_page_links) . ' ">...</a>';
          $display_links_string .= '</li>';
        }

// page nn button
        for ($jump_to_page = 1 + (($cur_window_num - 1) * $max_page_links); ($jump_to_page <= ($cur_window_num * $max_page_links)) && ($jump_to_page <= $this->number_of_pages); $jump_to_page++) {
          if ($jump_to_page == $this->current_page_number) {
            $display_links_string .= '<li class="page-item active">';
              $display_links_string .= '<a class="page-link" href="' . $link->set_parameter($this->page_name, $jump_to_page) . '" title=" ' . sprintf(PREVNEXT_TITLE_PAGE_NO, $jump_to_page) . ' ">' . $jump_to_page . '<span class="sr-only">(current)</span></a>';
            $display_links_string .= '</li>';
          } else {
            $display_links_string .= '<li class="page-item">';
              $display_links_string .= '<a class="page-link" href="' . $link->set_parameter($this->page_name, $jump_to_page) . '" title=" ' . sprintf(PREVNEXT_TITLE_PAGE_NO, $jump_to_page) . ' ">' . $jump_to_page . '</a>';
            $display_links_string .= '</li>';
          }
        }

// next window of pages
        if ($cur_window_num < $max_window_num) {
          $display_links_string .= '<li class="page-item">';
            $display_links_string .= '<a class="page-link" href="' . $link->set_parameter($this->page_name, $cur_window_num * $max_page_links + 1) . '" title=" ' . sprintf(PREVNEXT_TITLE_NEXT_SET_OF_NO_PAGE, $max_page_links) . ' ">...</a>';
          $display_links_string .= '</li>';
        }

// next button
        if (($this->current_page_number < $this->number_of_pages) && ($this->number_of_pages != 1)) {
          $display_links_string .= '<li class="page-item">';
            $display_links_string .= '<a class="page-link" href="' . $link->set_parameter($this->page_name, $this->current_page_number + 1) . '" aria-label=" ' . PREVNEXT_TITLE_NEXT_PAGE . ' "><span aria-hidden="true"><i class="fas fa-angle-right"></i></span></a>';
            $display_links_string .= '<span class="sr-only">' . PREVNEXT_TITLE_NEXT_PAGE . '</span>';
          $display_links_string .= '</li>';
        } else {
          $display_links_string .= '<li class="page-item disabled" aria-disabled="true">';
            $display_links_string .= '<span class="page-link"><i class="fas fa-angle-right" aria-hidden="true"></i></span>';
          $display_links_string .= '</li>';
        }

        $display_links_string .= '</ul>';
      $display_links_string .= '</nav>';

      return $display_links_string;
    }

    public function display_count($text_output) {
      $to_num = ($this->number_of_rows_per_page * $this->current_page_number);
      if ($to_num > $this->number_of_rows) {
        $to_num = $this->number_of_rows;
      }

      if ($to_num == 0) {
        $from_num = 0;
      } else {
        $from_num = $this->number_of_rows_per_page * ($this->current_page_number - 1);

        $from_num++;
      }

      return sprintf($text_output, $from_num, $to_num, $this->number_of_rows);
    }

    public static function create_sort_heading($sortby, $colnum, $heading, $class = 'dropdown-item') {
      if (!$sortby) {
        return $heading;
      }

      $link = $GLOBALS['Linker']->build()->retain_query_except(['info', 'page']);
      $link->set_parameter('sort', $colnum . ($sortby == $colnum . 'a' ? 'd' : 'a'));

      $selected = substr($sortby, 0, -1) == $colnum;
      $ascending = substr($sortby, -1) === 'a';
      $title = sprintf(($selected && $ascending) ? TEXT_DESCENDINGLY : TEXT_ASCENDINGLY, $heading);
      $text = sprintf(($selected ? ($ascending ? LISTING_SORT_DOWN : LISTING_SORT_UP) : LISTING_SORT_UNSELECTED), $heading);

      return '<a href="' . $link. '" title="' . Text::output($title) . '" class="' . $class . '" rel="nofollow">' . $text . '</a>';
    }

  }

Re-run your accessibility check and see if it is solved?
Also take a look at the page to make sure it still looks OK.
It is the page links when paging when you have loads of products.
I am not here to build for you.
I am here to build with you. Let's help each other.
ecartz
Core Team
Posts: 3084
Joined: Tue Nov 05, 2019 6:02 pm
Phoenix Version:
Has thanked: 4 times
Been thanked: 208 times

Re: The European Accessibility Act (EAA) requires web shops to be accessible to all from June 28, 2025

Post by ecartz »

burt wrote: Sat May 31, 2025 12:28 pm Can you explain on what page this can be found ?
https://github.com/CE-PhoenixCart/Phoen ... 6381dff420

It's the disabled link at https://github.com/CE-PhoenixCart/Phoen ... ts.php#L92

Also https://github.com/CE-PhoenixCart/Phoen ... s.php#L146
artfulweb
Contributor
Posts: 184
Joined: Thu Oct 29, 2020 12:34 pm
Phoenix Version: v1.1.0.6
Has thanked: 29 times
Been thanked: 14 times

Re: The European Accessibility Act (EAA) requires web shops to be accessible to all from June 28, 2025

Post by artfulweb »

ecartz wrote: Sat May 31, 2025 12:46 pm
burt wrote: Sat May 31, 2025 12:28 pm Can you explain on what page this can be found ?
https://github.com/CE-PhoenixCart/Phoen ... 6381dff420

It's the disabled link at https://github.com/CE-PhoenixCart/Phoen ... ts.php#L92

Also https://github.com/CE-PhoenixCart/Phoen ... s.php#L146
This was the solution that works, not the preceeding post. Now all pages using the the split page numbering (like products, specials, etc.) are free of accessibility errors. Thanks Matt!
artfulweb
Contributor
Posts: 184
Joined: Thu Oct 29, 2020 12:34 pm
Phoenix Version: v1.1.0.6
Has thanked: 29 times
Been thanked: 14 times

Re: The European Accessibility Act (EAA) requires web shops to be accessible to all from June 28, 2025

Post by artfulweb »

burt wrote: Sat May 31, 2025 12:27 pm
artfulweb wrote: Sat May 31, 2025 9:23 am <input name="email_address" type="email" id="inputEmail" autocomplete="username email" placeholder="" minlength="6" required="" aria-required="true" class="form-control">

Fix the following:
the autocomplete attribute is incorrectly formatted
I'm not sure which would be best to use here so I asked chatGPT;
If you're autofilling a username eg to sign in, use:
autocomplete="username"

That was it Burt. In includes/modules/customer_data/cd_email_address.php I changed line 83 to 'autocomplete' => 'email',

Now no accessibility errors on the create_account.php page.


If you're autofilling an email address in a non-login context eg creating an account, use:
autocomplete="email"


Join The Code Co-op to get access to your library in the Code Co-op Forum
Post Reply