wcag-ui

Making A11y... Accessible

Pixu

Emiliano Pisu

Father of 2 lovely monsters

D&D master, player ... whatever

Senior Design Engineer, Sensei & Co-Host
@ Dev Dojo IT

pixu1980@linkedin linkedin.com/in/pixu1980
pixu1980@github github.com/pixu1980
pixu1980@codepen codepen.io/pixu1980
devdojo@linkedin linkedin.com/company/dev-dojo-it/
devdojo@youtube youtube.com/@devdojo_it
devdojo@twitch twitch.tv/devdojo_it
devdojo@telegram @devdojo_it

Introducing wcagUI

Crafting UIs with Accessibility built-in!

What is wcagUI?

  • it's a Design System (thanks to Exeen)
  • it's a UI Kit, based on WCAG APG Patterns
  • it's made with DevX in mind, easing the implementation of accessible and inclusive experiences

What is accessibility?

Accessibility is a declaration of intents, not an overlay!

  • Over 1.3 billion people (16-20%) of global population faces digital barriers every day
  • Too many companies thinks accessibility as an "extra" instead of a core requirement
  • European Accessibility Act (EAA) starting since 2025-06-28

key features of wcagUI

0-dependencies

Crafted in pure HTML, CSS, and JavaScript, it's entirely self-contained and modular, with a monorepo architecture

📜 Semantic at its core

Every component is built extending Semantic HTML elements (also with WAI-ARIA attributes when needed), helping to improve accessibility, SEO and SSR

🤖 Accessibile by Design

Thanks to the incomparable work of Exeen's Team and based on WCAG Patterns, wcagUI aims to make crafting UIs effortless in terms of accessibility

🚀 Cutting-Edge Web APIs

Leveraging the power of the latest core web technologies; it's based on a simple assumption:

support the last 2 majors of any viewport engine

How does it work?

customElements v1✨

  • Transparent Structure: No encapsulation means no styling boundaries, better accessibility and natural content parsing.
  • Simple Debugging: Inspect elements directly with developer tools, as usual for any common HTML element.
  • VanillaJS & Semantic HTML: Extends native Semantic HTML elements, without using shadowDOM, to stay as close as possible to Accessibility and SEO-friendly principles and best practices.

Integration ⚡️🛠

  • Efficient DOM Manipulation: every update in components takes less than a requestAnimationFrame.
  • Custom Events Driven: any of the components use custom events for seamless data flow.
  • Observable & Observed: every external update to the internal HTML structure is "mutation observed" and the component updates itself accordingly.

Modular Styling with CSS Custom Properties & @layer 🎨📐

  • Real-Time Adaptability: Dynamic styling reacts instantly to user interactions.
  • Exposed CSS APIs: Easily adjust colors, fonts, and layouts with CSS custom properties.
  • Organized CSS Layers: `@layer` ensures better structure and reduced conflicts, and flow with the cascade algorithm.

Monorepo Architecture & Optimization 🏗️📦

  • Modular by Design: The entire design system/UI kit follows a monorepo structure, ensuring seamless scalability and maintainability.
  • Tree Shaking & Deduplication: The modular architecture allows efficient bundling, reducing unused code and optimizing performance.
  • Native Compatibility: Designed to work efficiently with modern build tools and frameworks.

Recap of what we did: extending Semantic HTML elements, metaprogramming, static initialization blocks, mixins, decorators, lifecycle methods, css @layer, css custom properties, exposing css API?

Sounds like a huge "vanilla" ice cream, uh?

.talk:is(:not([enough]));
showMe('the <code></code>');

extending Semantic HTML elements

      
export class Button extends HTMLButtonElement {
}

export class Input extends HTMLInputElement {
}

export class Dialog extends HTMLDialogElement {
}

export class Details extends HTMLDetailsElement {
}

    

Yep, no base classes!

Metaprogramming!

      
/**
* wcagUI Button class
*
* @export
* @class Button 
* @extends {HTMLButtonElement}
*/
export class Button extends HTMLButtonElement {
  static name = "wcag-button";
  static extends = "button";
 
  static attributes = {
    disabled: (oldValue, newValue) => {
      console.log("disabled changed", oldValue, newValue, this.textContent); 
    }
  }
 
  static events = {
    click: function (e) {
      console.log("button clicked", this.textContent);
    },
  }
  
  // yee, metaprogramming!
 
 ...
}
      
    

Static Initialization Blocks (MDN)

      
export class Button extends HTMLButtonElement {
  static name = "wcag-button";

  ...
  
  /**
  * static initialization
  *
  * @static
  * @memberof Button
  */
  static {
    componentDecorator("Button", Button);
  } 
  
 ...
}

// import '@wcag-ui/button';

// yee, static initialization blocks!
  
    

Decorators?

      
export function componentDecorator(componentName, component) {
  assertMetaKey(component, "name");
  defineCustomElement(component);
  applyMixinsToComponent(component);
  exposeComponent(componentName, component);
}

export function defineCustomElement(component) {
  !customElements?.get(component.name) &&
    customElements.define(
      component.name,
      component, 
      buildExtendOptions(component)
    );
}

export function buildExtendOptions(mixins) {
  return mixins.extends ? { extends: mixins.extends } : undefined;
  
  // this allows to 
  // instead of 
}

export function applyMixins(component) {
  Object.assign(
    component.prototype,
    { componentName: component.name },
    component.attributes && { ...buildAttributeHandlers(component.attributes) },
    component.events && { ...buildEventHandlers(component.events) },
    buildLifecycleMethods(component)
  );

  component.attributes &&
    Object.defineProperty(component, "observedAttributes", {
      get: () => Object.keys(component.attributes),
    });
}

// yee, decorators!
  
    

Lifecycle methods?

      
export function buildLifecycleMethods(component) {
  return {
    handleEvent(e) {
      this[`handle${pascalize(e.type)}Event`]?.(e);
    },
    attributeChangedCallback(name, oldValue, newValue) {
      this[`handle${pascalize(name)}AttributeChanged`]?.(oldValue, newValue);
    },
    connectedCallback() {
      for (const event of Object.keys(component.events)) {
        this.addEventListener(event, this);
      }
    },
    disconnectedCallback() {
      for (const event of Object.keys(component.events)) {
        this.removeEventListener(event, this);
      }
    },
  };
}
  
// yee, lifecycle!
  
    

Exposing CSS APIs?

We needed to re-learn the C in CSS

The Cascade is an algorithm that defines how user agents combine property values originating from different sources. MDN

What are the players involved
in the algorithm?

  • user-agent stylesheets
  • user stylesheets
  • author stylesheets (+ inline styles)

How the cascade algorithm handles the precedence of declarations?

  1. user-agent declarations
  2. user declarations
  3. author and inline declarations
  4. @keyframe animations
  5. !important 😞 author and inline declarations
  6. !important 😢 user declarations
  7. !important 😭 user-agent declarations
  8. transitions

How the @layer
integrates into this?

  1. @layer 1 declarations
  2. @layer ... declarations
  3. @layer N declarations
  4. un-layered declarations
  5. inline declarations

What about
@layer and !important?

  1. !important 😟 un-layered declarations
  2. !important 😞 @layer N declarations
  3. !important 😖 @layer ... declarations
  4. !important 😫 @layer 1 declarations
  5. !important 😢 inline declarations

So in the end...

Order of precedence (lower to higher priority)

  1. user-agent declarations
  2. user declarations
  3. author declarations
    1. @layer 1 declarations
    2. @layer ... declarations
    3. @layer N declarations
    4. un-layered declarations
    5. inline declarations
  4. @keyframe animations
  5. !important 😟 author and inline declarations
    1. !important 😞 un-layered declarations
    2. !important 😖 @layer N declarations
    3. !important 😫 @layer ... declarations
    4. !important 😢 @layer 1 declarations
    5. !important 😭 inline declarations
  6. !important 😡 user declarations
  7. !important 🤬 user-agent declarations
  8. transitions

exposing CSS API?

      
@layer wcag-ui.core, wcag-ui.foundations, wcag-ui.components;

@layer wcag-ui.foundations {
  @layer typography {
  :root {
    --wcag-t--font-family: "Inter";
    --wcag-t--font-size--base: 1.6rem;
    
    ...
  }
}

@layer wcag-ui.components {
  @layer button {
    :root {
      ...
      
      --wcag-button--border-radius: .4rem;
      --wcag-button--background-color: var(--wcag-c--blue-400);
      
      ...
    }
    
    [is="wcag-button"] {
      ...
      
      border-radius: var(--wcag-button--border-radius);
      background-color: var(--wcag-button--background-color);
      
      ...
    }
  }
}

// un-layered styles
:root {
  --wcag-button--background-color: rebeccapurple;
}
  
// yee, css APIs!
  
    

Design System Foundations

Colors

A11y-compliant palettes with clear contrast ratios. Semantic color tokens (e.g., primary, surface, danger) enable consistent theming across components

Elevations

Defined elevation tokens using layered shadows and z-index management, providing consistent depth and focus cues

Spacings

An exponential spacing scale 4px based, enabling consistent paddings, margins, and layout rhythm

Typography

A readable type scale and fonts stack ensures readability across all devices

Components

Button

      
        <button is="wcag-button">
          Button
        </button>
        <button is="wcag-button" secondary>
          Button
        </button>
        <button is="wcag-button" tertiary>
          Button
        </button>
        <button is="wcag-button" destructive>
          Button
        </button>
      
    

Input

      
        <input is="wcag-input" name="username"
          type="text"
          aria-label="Username" 
          placeholder="Enter your username..." />
      
    
      
        <label>
          Username        
          <input is="wcag-input" name="username" type="text"
            placeholder="Enter your username..." />
        </label>
      
    

Select Dropdown

      
        <select is="wcag-select"
          name="options" aria-label="Options"
          placeholder="Select an option"
        >
          
          
          
        </select>
      
    
      
        
      
    

Checkbox

      
        <input is="wcag-checkbox" name="accept"
          aria-label="Accept agreement" />
      
    
      
        <label>
          Accept agreement
          <input is="wcag-checkbox" name="accept"
            type="checkbox" />
        </label>
      
    

Radio

      
        <input is="wcag-radio" name="accept"
          aria-label="Accept agreement" />
      
    
      
        <label>
          Accept agreement
          <input is="wcag-radio" name="accept"
            type="radio" />
        </label>
      
    

Components Implemented

Button, Input, Textarea, Select, Checkbox, Radio, Switch, Details, Accordion, Dialog, Tooltips, Popovers,

Components Roadmap

Carousel, Calendar, DatePicker (single and range), DateTimePicker (single and range), ColorPicker (in RGB, HEX, HSL, OKLCH formats), Alert & Toast, Tabs & Stepper, and many many more...

Links

wcag-ui.com
wcag-ui.com

Slides Links

Slides
Slides
Slides Repo on Github
Slides Repo

Links

devdojo.it
devdojo.it
exeen.it
exeen.it
© 1987-2025 - Warner Bros. Entertainment Inc.
Pixu

Grazie ❤️!

Dev Dojo IT Logo