Keyboard navigation pro forms and conditional wrapper issue

Hey once again,

I am creating a multistep form and I have the following problem:

It seems to me like that some fields are not accessible by default as I couldnt navigate the form with tab navigation. Hence, I. added tabindex=0 to my cards. Now, I can focus the card via keyboard nav and also navigate to the next step. However, if I navigate like this conditonal wrappers logic doesnt work anymore. I tried to use tabindex on both the card wrapper as well as the text inside.

Here is a loom to see the issue:

  1. I click in the first step’s choice which then skips some steps set with conditional logic and opens a step with an embed

  2. I set tabindex on the cards to navigate them via keyboard. If I click enter on the first step’s choice, its not registering that the value = true and should therefore show the step with the embed but skips this step.

Is this a deeper accessibility issue or am I misunderstanding/overlooking something here?

Thanks for your help as always :slight_smile:

Edit: Also, I was wondering how the datepicker can be used via keyboard navigation. I currently have it set to be shown by default and not updon click but I cannot navigate the dates via keyboard nav (same form)

[sensitive]
Loom:

Url to test it by clicking on the fat orange button:

[/sensitive]

@manc @Daniele any news on this topic :)? I dont want to pressure here, just asking because a newer topic has been answered while this one stays unanswered.

Hey Dave :slight_smile:

Are you using Card Radio / Checkboxes? If so, you have a group “Accessibility” to setup desired accessibility settings for your cards:

Normally, there is no additional custom work needed.

Regarding the Date Picker: Unfortunately, Flatpickr, the library we are using, is not really great in terms of accessibility. We plan to switch to a different, more modern library, but we cannot tell any ETA yet :slight_smile:

@Daniele Thanks for your response! I actually didnt see this setting. After using a focus border I can, however, still note that only the first card radio is focused and the remaining cards are ignored. The conditional steps are also not taking into account on pressing enter.
[sensitive]
Here is the updated Form:

[/sensitive]

You need to set the Accessibility Outlone for each of your Card Radios, for example using a global class :slight_smile: It should work after that.

Regarding the conditional steps, I`ll take a deeper look :slight_smile:

@Daniele thanks for taking a deeper look! If you need access to the site, lmk.

Ive set outline to be 1px solid orange but the keyboard nav continues to jump to the first element only

Access would be helpful. Thanks :slight_smile:

@Daniele here u go and thanks again for the support :slight_smile:

[sensitive]

Login: https://a9h7llpydi-staging.onrocket.site/wp-admin
page to inspect the form: Jetzt Termin in Berlin buchen
user: support
pw: 249$XcI4#0lmOWL^K7LaqsJ5

[/sensitive]

Hey :slight_smile: Oh, now, I notice that you`re using Radio Buttons. That explains everything and makes absolutely sense :slight_smile: For Radio Buttons, thats the HTML default that only the first one reacts on the tabulator. All others can be choosen by using the ARROW UP and ARROW DOWN keys.

But there is a problem. This will trigger a “click” or “change” event..and Pro Forms will jump to the next step. That`s not what you want here.

Therefore, I’ve just improved the accessibility aspect for radio buttons in combination with “Trigger Next Step”. I`ve included the bricksforge 3.1.1-beta.1 to your site, which includes the fix. Its a stable version that will be officially deployed tomorrow.

This will result to:

@Daniele thanks for the update and the explanation! While i couldnt see the video (it shows 0sec length) but everything works now!

One last note before the topic can be closed: I wrote a script with chatGPT that adds the necessary accessibility functions to flatpickr. probably not the cleanest and by it is by itself obv not optimal that flatpickr is not accessible but it works as a workaround. Maybe it helps anyone who stumbles upon this thread:

CSS
.fp-nav-button {
background: transparent;
border: none;
cursor: pointer;
}

/* Screen-reader–only text */
.fp-sr-desc {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
border: 0;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}

Script

document.addEventListener('DOMContentLoaded', () => {
  const DELAY = 400;

  setTimeout(() => {
    document.querySelectorAll('input.flatpickr.flatpickr-input').forEach(input => {
      const fp = input._flatpickr;
      if (!fp) return;

      // Re-apply enhancements on every redraw
      ['onReady','onOpen','onMonthChange','onYearChange']
        .forEach(hook => fp.config[hook].push(() => setTimeout(enhance, DELAY)));

      // Initial enhancement
      setTimeout(enhance, DELAY * 1.5);

      function enhance() {
        const cal = fp.calendarContainer;
        if (!cal) return;

        // 1) Dialog roles & input ARIA
        cal.setAttribute('role', 'dialog');
        cal.setAttribute('aria-label', 'Date picker');
        input.setAttribute('aria-haspopup', 'grid');
        const cid = cal.id || (cal.id = 'fp-' + Math.random().toString(36).slice(2));
        input.setAttribute('aria-controls', cid);
        input.setAttribute('aria-expanded', 'true');

        // 2) Grid & cells
        const dw = cal.querySelector('.flatpickr-days .dayContainer');
        if (!dw) return;
        const grid = dw.parentElement;
        grid.setAttribute('role', 'grid');

        const weekCount = cal.querySelectorAll('.flatpickr-weekday').length || 7;
        const days = Array.from(dw.querySelectorAll('.flatpickr-day'));

        days.forEach(d => {
          d.setAttribute('role', 'gridcell');
          d.setAttribute('aria-selected', d.classList.contains('selected') ? 'true' : 'false');
          if (d.classList.contains('prevMonthDay') || d.classList.contains('nextMonthDay')) {
            d.setAttribute('aria-disabled', 'true');
          } else {
            d.removeAttribute('aria-disabled');
          }
          d.tabIndex = -1;
        });

        const first = days.find(d => d.classList.contains('selected'))
                    || days.find(d => d.classList.contains('today'))
                    || days[0];
        if (first) first.tabIndex = 0;

        // 2a) Keyboard navigation + Shift-Tab
        dw.onkeydown = e => {
          if (e.key === 'Tab') {
            e.stopPropagation();
            const focusables = [
              cal.querySelector('button.fp-nav-button[aria-label="Previous month"]'),
              cal.querySelector('.flatpickr-monthDropdown-months'),
              cal.querySelector('.numInput.cur-year'),
              cal.querySelector('button.fp-nav-button[aria-label="Next month"]'),
              dw.querySelector('[tabindex="0"]')
            ].filter(Boolean);

            let idx = focusables.indexOf(document.activeElement);
            if (idx === -1) idx = focusables.length - 1;

            if (e.shiftKey) {
              const prev = (idx - 1 + focusables.length) % focusables.length;
              focusables[prev].focus();
              e.preventDefault();
            }
            return;
          }

          // stop Splide/carousel listening
          const navKeys = ['ArrowRight','ArrowLeft','ArrowDown','ArrowUp','Home','End','PageUp','PageDown','Enter',' '];
          if (navKeys.includes(e.key)) e.stopPropagation();

          const idx = days.indexOf(dw.querySelector('[tabindex="0"]'));
          let next;

          switch (e.key) {
            case 'ArrowRight': next = days[idx+1]; break;
            case 'ArrowLeft':  next = days[idx-1]; break;
            case 'ArrowDown':  next = days[idx+weekCount]; break;
            case 'ArrowUp':    next = days[idx-weekCount]; break;
            case 'Home':       next = days[0]; break;
            case 'End':        next = days[days.length-1]; break;
            case 'PageUp':     fp.changeMonth(-1); return;
            case 'PageDown':   fp.changeMonth(1);  return;
            case 'Enter':
            case ' ':          dw.querySelector('[tabindex="0"]').click(); return;
            default: return;
          }

          if (next) {
            e.preventDefault();
            const cur = dw.querySelector('[tabindex="0"]');
            cur.tabIndex = -1;
            next.tabIndex = 0;
            next.focus();
          }
        };

        // 3) Prev/Next buttons
        [['.flatpickr-prev-month', -1, 'Previous month'],
         ['.flatpickr-next-month',  1, 'Next month']
        ].forEach(([sel, dir, label]) => {
          const span = cal.querySelector(sel);
          if (!span) return;
          const btn = document.createElement('button');
          btn.type = 'button';
          btn.classList.add('fp-nav-button');
          btn.setAttribute('aria-label', label);
          btn.innerHTML = span.innerHTML;
          btn.addEventListener('click', e => {
            e.stopPropagation();
            fp.changeMonth(dir);
          });
          btn.addEventListener('keydown', e => {
            if (e.key === 'Enter' || e.key === ' ') {
              e.preventDefault();
              btn.click();
            }
          });
          span.replaceWith(btn);
        });

        // 4) Month & year selectors
        const monthSel = cal.querySelector('.flatpickr-monthDropdown-months');
        if (monthSel) {
          monthSel.tabIndex = 0;
          monthSel.setAttribute('aria-label','Select month');
          monthSel.addEventListener('keydown', e => e.stopPropagation());
        }
        const yearInp = cal.querySelector('.numInput.cur-year');
        if (yearInp) {
          yearInp.tabIndex = 0;
          yearInp.setAttribute('aria-label','Select year');
          yearInp.addEventListener('keydown', e => e.stopPropagation());
        }

        // 5) Screen-reader instructions
        if (!cal.querySelector('.fp-sr-desc')) {
          const desc = document.createElement('div');
          desc.id = cal.id + '-desc';
          desc.className = 'fp-sr-desc';
          desc.textContent = 'Use arrow keys to navigate dates; PageUp/PageDown to change month; Home/End for first/last day; Enter or space to select.';
          cal.appendChild(desc);
          grid.setAttribute('aria-describedby', desc.id);
        }
      }
    });
  }, DELAY);
});

Oh, thats a great around :slight_smile: Are you allowing us to move this thread to “Bug Reports”? So it becomes visible to others and anyone can benefit from your solution :slight_smile:

@Daniele i thought everyone could see this thread already :smiley: sure, go ahead and move it. the intention was that others could make use of this snippet.

1 Like