Skip to main content

Overview

Dynamic UI is built with accessibility as a core principle. Every component includes ARIA attributes, keyboard navigation, focus management, and screen reader support to ensure WCAG 2.1 AA compliance.

ARIA Attributes

All interactive components include appropriate ARIA attributes for screen reader compatibility.

Button States

The DButton component automatically manages accessibility attributes:
src/components/DButton/DButton.tsx
<button
  disabled={isDisabled}
  aria-label={ariaLabel}
  aria-busy={loading}
  aria-disabled={isDisabled}
>
  {loading && (
    <span className="btn-loading">
      <span
        className="spinner-border spinner-border-sm"
        aria-hidden="true"
      />
      {loadingText && <span role="status">{loadingText}</span>}
    </span>
  )}
</button>
Accessibility Features:
  • aria-label: Provides accessible name (especially important for icon-only buttons)
  • aria-busy: Indicates loading state to screen readers
  • aria-disabled: Announces disabled state
  • aria-hidden="true": Hides decorative loading spinner from screen readers
  • role="status": Announces loading text as status update

Progress Indicators

src/components/DProgress/DProgress.tsx
<div
  role="progressbar"
  aria-label="Progress bar"
  aria-valuenow={currentValue}
  aria-valuemin={minValue}
  aria-valuemax={maxValue}
>
  {/* Progress content */}
</div>

Toast Notifications

src/components/DToast/DToast.tsx
<div
  role="alert"
  aria-live="assertive"
  aria-atomic="true"
>
  {/* Toast content */}
</div>
  • role="alert": Identifies as important notification
  • aria-live="assertive": Screen readers interrupt to announce
  • aria-atomic="true": Reads entire content on change

Tabs

src/components/DTabs/DTabs.tsx
<button
  role="tab"
  aria-controls={`${option.tab}Pane`}
  aria-selected={option.tab === selected}
>
  {option.label}
</button>

<div
  role="tabpanel"
  tabIndex={0}
  aria-labelledby={`${tab}Tab`}
>
  {/* Tab content */}
</div>

List Groups

src/components/DListGroup/components/DListGroupItem.tsx
<a
  {...active && { 'aria-current': true }}
  {...disabled && { 'aria-disabled': true }}
>
  {/* List item content */}
</a>

Keyboard Navigation

Dynamic UI provides comprehensive keyboard support for all interactive components.

Focus Management

The portal system includes automatic keyboard trap for modals and overlays:
src/contexts/DPortalContext.tsx
useEffect(() => {
  const keyEvent = (event: KeyboardEvent) => {
    const lastPortal = document.querySelector(`#${portalName} > div > div:last-child`);
    
    if (event.key === 'Escape') {
      if (lastPortal) {
        handleClose(lastPortal as HTMLElement);
        return;
      }
    }
    
    if (event.key === 'Tab') {
      const focusableElements = getKeyboardFocusableElements(lastPortal as HTMLElement);
      if (focusableElements.length === 0) return;
      
      const firstElement = focusableElements[0];
      const lastElement = focusableElements[focusableElements.length - 1];
      
      if (event.shiftKey && document.activeElement === firstElement) {
        event.preventDefault();
        lastElement.focus();
      } else if (!event.shiftKey && document.activeElement === lastElement) {
        event.preventDefault();
        firstElement.focus();
      }
    }
  };
  
  if (stack.length !== 0) {
    window.addEventListener('keydown', keyEvent);
  }
  
  return () => {
    window.removeEventListener('keydown', keyEvent);
  };
}, [handleClose, portalName, stack.length]);
Keyboard Trap Benefits:
  • Prevents focus from escaping modal dialogs
  • Tab cycles through only focusable elements within the modal
  • Shift+Tab works in reverse direction
  • Escape key closes the top-most modal

Focusable Elements Detection

The library includes a utility to find all keyboard-accessible elements:
src/utils/getKeyboardFocusableElements.ts
export default function getKeyboardFocusableElements(
  container?: HTMLElement,
): HTMLElement[] {
  if (!container) {
    return [];
  }
  return [
    ...container.querySelectorAll<HTMLElement>(
      'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])',
    ),
  ].filter((element) => !element.hasAttribute('disabled'));
}
Detected Elements:
  • Links (<a>)
  • Buttons (<button>)
  • Form inputs (input, textarea, select)
  • Details/summary elements
  • Elements with explicit tabindex (excluding -1)
  • Excludes disabled elements

Input PIN Focus Management

The DInputPin component provides automatic focus progression:
src/components/DInputPin/DInputPin.tsx
const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
  const { currentTarget } = event;
  const { value } = currentTarget;
  
  if (value.length === 1) {
    // Move focus to next input
    (input.nextSibling as HTMLElement)?.focus();
  }
}, []);

const handleKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) => {
  const { currentTarget, key } = event;
  
  if (key === 'Backspace' && !currentTarget.value) {
    // Move focus to previous input on backspace
    (currentTarget.previousSibling as HTMLElement)?.focus();
    currentTarget.focus();
  }
}, []);
<div
  tabIndex={-1}
  aria-labelledby={`${name}Label`}
  aria-hidden="false"
  data-bs-keyboard="false"
>
  {/* Modal content */}
</div>
  • tabIndex={-1}: Allows programmatic focus but not tab navigation to container
  • data-bs-keyboard: Controls keyboard dismissal behavior

Screen Reader Support

Status Announcements

Components announce state changes to screen readers:
{loading && (
  <span role="status" aria-hidden="true">
    <span className="spinner-border" />
  </span>
)}

Loading States

Select and input components include loading indicators:
src/components/DInputSelect/DInputSelect.tsx
<div
  role="status"
  aria-hidden="true"
>
  <span className="spinner-border spinner-border-sm" />
</div>

Form Validation

Inputs connect to validation messages via aria-describedby:
src/components/DInputPin/DInputPin.tsx
<input
  aria-describedby={`${id}State`}
  // ... other props
/>

Color Contrast

Dynamic UI ensures WCAG AA compliant color contrast ratios.

Minimum Contrast Ratio

src/style/abstracts/variables/_colors.scss
// WCAG 2.0 compliant contrast ratio
$min-contrast-ratio: 4.5 !default;

$color-contrast-dark: $gray-700 !default;
$color-contrast-light: $white !default;
The library uses a 4.5:1 contrast ratio, meeting WCAG 2.0 Level AA standards for normal text.

Automatic Contrast Calculation

Text colors are automatically calculated for optimal contrast:
$default-text-color: color-contrast-var(map-get($all-colors, primary-500));
This function:
  1. Measures the luminance of the background color
  2. Tests contrast against both light and dark text
  3. Returns the option meeting the minimum contrast ratio

Theme Color Contrasts

src/style/abstracts/variables/_colors.scss
$primary-text-emphasis: var(--#{$prefix}primary-600) !default;
$success-text-emphasis: var(--#{$prefix}success-600) !default;
$warning-text-emphasis: var(--#{$prefix}warning-800) !default;
$danger-text-emphasis: var(--#{$prefix}danger-600) !default;
Each theme color has emphasis variants ensuring sufficient contrast on light backgrounds.

Interactive Targets

All interactive elements meet the minimum touch target size.

Button Sizing

// Minimum touch target: 44x44px (WCAG 2.5.5)
$btn-padding-y: 0.5rem;    // 8px
$btn-padding-x: 1rem;      // 16px
$btn-font-size: 1rem;      // 16px
$btn-line-height: 1.5;     // Results in ~44px height

Form Controls

$input-padding-y: 0.5rem;
$input-padding-x: 0.75rem;
$input-line-height: 1.5;
$input-height: calc(1.5em + 1rem + 2px); // ~46px

Motion and Animation

Respects user preferences for reduced motion:
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
Portal animations using Framer Motion respect this preference automatically.

Testing Accessibility

Jest-axe Integration

Dynamic UI includes jest-axe for automated accessibility testing:
import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

test('DButton has no accessibility violations', async () => {
  const { container } = render(<DButton>Click me</DButton>);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

Storybook a11y Addon

All components are tested with the Storybook accessibility addon:
package.json
"@storybook/addon-a11y": "~10.2.8"
This provides:
  • Real-time accessibility violation detection
  • WCAG level filtering (A, AA, AAA)
  • Detailed violation descriptions
  • Element highlighting

Best Practices

Use aria-label or aria-labelledby for all interactive elements, especially icon-only buttons:
<DButton 
  iconStart="X" 
  aria-label="Close dialog"
/>
Ensure logical tab order by placing elements in DOM order. Use tabIndex={-1} for containers that shouldn’t be in tab flow:
<div tabIndex={-1}>
  <button>First focus</button>
  <button>Second focus</button>
</div>
Use role="status" or aria-live regions for dynamic content updates:
<div role="status" aria-live="polite">
  {itemCount} items in cart
</div>
Navigate your entire application using only the keyboard:
  • Tab through all interactive elements
  • Activate with Enter/Space
  • Close modals with Escape
  • Navigate forms with arrow keys
Prefer native HTML elements over custom implementations:
  • Use <button> not <div onClick>
  • Use <a> for navigation
  • Use <input>, <select> for forms

WCAG 2.1 Compliance

Dynamic UI components meet WCAG 2.1 Level AA criteria:

1.4.3 Contrast

Minimum 4.5:1 contrast ratio for normal text, 3:1 for large text

2.1.1 Keyboard

All functionality available via keyboard

2.1.2 No Keyboard Trap

Focus can move away from any component using keyboard

2.4.3 Focus Order

Focus order preserves meaning and operability

2.4.7 Focus Visible

Keyboard focus indicator visible with 2px outline

2.5.5 Target Size

Interactive targets minimum 44x44 CSS pixels

4.1.2 Name, Role, Value

All components have proper ARIA attributes

4.1.3 Status Messages

Status changes announced to assistive technologies

Resources

WCAG 2.1 Guidelines

Official WCAG quick reference

ARIA Authoring Practices

W3C ARIA patterns and widgets

WebAIM

Accessibility training and resources

axe DevTools

Browser extension for accessibility testing

Next Steps

Architecture

Learn about the micro frontend architecture

Theming

Customize colors while maintaining accessibility

Build docs developers (and LLMs) love