Skip to main content
Light Dark System

Roadmap

Cordwainer Elements 1.0 launched as a hard fork of Shoelace, with all 58 components renamed and re-themed under the cw- prefix. Since then, we’ve shipped 5 new components: <cw-combobox>, <cw-date-picker>, <cw-time-picker>, <cw-pagination>, and <cw-data-table>. This page outlines what we’re planning to build during the v1.x release line — new components, enhancements, and infrastructure work — roughly in priority order.

Note on sequencing (August 2026): v1.3 has been reprioritized ahead of some previously-planned work. Building a real site on Cordwainer Elements surfaced a hard gap — no way to assemble page navigation, branding, or a footer — so that work has moved up, and the components originally scoped for v1.2/v1.3 that aren’t part of that gap have shifted down to v1.5. A Shoelace-to-Cordwainer migration toolkit has also been added as v1.4, ahead of that shifted component work. A dedicated Media release (v1.6) was also split out afterward, rather than scattering image/audio/video components across whichever release happened to be convenient.

How releases are planned

Each v1.x minor release is themed around a category of gaps in the current component set. Every new component that ships will, at minimum:

  • Be built on the same CwElement/CwFormControl foundations as existing components, with full TypeScript types
  • Ship with React, Vue, Angular, and Svelte wrapper support (see Frameworks)
  • Be translated across all 29 supported locales
  • Include a documentation page with live, editable examples
  • Be reviewed against our Accessibility Commitment (keyboard support, ARIA, screen reader testing)

v1.1 — Forms & Input Essentials

The most-requested gaps in web component libraries are autocomplete/combobox, date and time selection, and pagination. These are foundational form/input pieces that a lot of real applications need before they can fully adopt the library.

<cw-combobox>

A filterable, searchable input that combines <cw-input> with a <cw-popup>-based listbox:

  • Type-ahead filtering of options as the user types
  • Optional free-text entry vs. constrained-to-list selection
  • Multi-select mode using <cw-tag> chips for selected values
  • Support for asynchronous option loading (e.g., remote search)

<cw-date-picker> and <cw-time-picker>

Calendar- and clock-based pickers that integrate with the existing form-association internals shared by <cw-input>, <cw-select>, etc.:

  • Single date, date range, and time selection modes
  • Min/max date constraints and disabled-date callbacks
  • Localized month/day names and date formats via cw-localize, reusing the locale data already used by <cw-format-date>
  • Keyboard navigation (arrow keys, Page Up/Down for month, Home/End)

<cw-pagination>

A standalone page-navigation control for lists, search results, and (in v1.2) data tables:

  • Configurable page size, sibling/boundary page counts, and ellipsis collapsing
  • Works as a controlled component (emits page-change events; doesn’t own the data)

Branding refresh

The current logo is a placeholder from the fork. During v1.1, Matt Hand will design and roll out the new Cordwainer Elements identity — logo, favicon, docs header, and social preview images. (No engineering dependency on the rest of the v1.1 component work; can land independently.)

v1.2 — Data Display

With input essentials in place, the next gap is presenting structured data. This release is narrowed to a single component:

<cw-data-table> and <cw-data-table-column>

A slot/template-driven table component:

  • Declarative column definitions with custom cell templates via slots
  • Sortable columns (client-side initially; emits events so consumers can wire up server-side sorting)
  • Row selection (single and multi, with a <cw-checkbox> column)
  • Pairs with <cw-pagination> from v1.1
  • Virtualization for large datasets is deferred to v1.7 — v1.2 targets correctness and API shape first

<cw-accordion>, <cw-avatar-group>, and <cw-stepper> were originally scoped for this release too — they’ve moved to v1.5 to make room for the navigation/page-chrome work and Shoelace migration tooling below.

v1.3 — Site Navigation & Page Chrome

Building a real site on Cordwainer Elements surfaced a gap: the library has no way to assemble the navigation, branding, and footer that essentially every site needs. This release is inspired primarily by the Red Hat Design System’s navigation category, which treats page chrome as a first-class, richly-decomposed set of components rather than a single generic menu — a departure from the Shoelace-derived “everyday primitives” approach the rest of the library follows.

Scope for each component below will be defined individually as it’s picked up. Site search itself doesn’t need a new component here — <cw-combobox> (v1.1) already covers it. Initial list:

  • <cw-primary-navigation> — top-level site navigation
  • <cw-secondary-navigation> — second-level nav for linking related pages beneath primary nav
  • <cw-logo> — branding mark component
  • <cw-header> — page header region
  • <cw-footer> — site footer

Open scoping question: RHDS itself folds branding directly into its primary-nav component and has no separate header element at all. Worth deciding, when <cw-header> is scoped, whether it’s a layout wrapper composing <cw-logo> + <cw-primary-navigation>, or whether that slot belongs directly on <cw-primary-navigation> instead, matching RHDS more closely.

“CW” shorthand logo mark

A compact wordmark — just “CW” — for spots the full logo doesn’t fit: favicon, browser tab, small graphic contexts. Distinct from <cw-logo> above, which carries the full brand mark; this is the icon-sized fallback it needs.

v1.4 — Shoelace Migration Tooling

Cordwainer Elements is a rename-and-retheme fork of Shoelace: same component shapes and attributes, different tag names (sl-cw-), CSS custom property namespace (--sl---cw-), custom event names (sl-cw-), and TypeScript class/type names (Sl*Cw*) — including for anyone who’s built their own components on top of Shoelace’s. Today, adopting Cordwainer in an existing Shoelace project means finding and renaming all four by hand across every file type in the project. This release ships a migration toolkit that automates it.

Quality bar for this release (in place of the component checklist above): extensive automated test coverage for every script — fixture projects exercising the parser edge cases described below (mismatched tag/property prefixes, extends-based consumer components, ambient type augmentation, etc.) across each supported file type — plus a working --dry-run mode that changes nothing and only reports, and a clear rollback path (git is the assumed safety net; the tool should refuse to run against a dirty working tree unless --force is passed).

TODO: research public open-source projects built on Shoelace that could serve as a real-world test case for the migration toolkit — a live, previously-unconverted codebase to run detect-shoelace-usage/migrate --dry-run against, beyond the synthetic fixture suite above. (matthand.com does not use Shoelace, so it isn’t a candidate here.)

Migration script suite

Individually runnable scripts, plus one orchestrator. Splitting by rename domain rather than by file type, since each domain needs different tooling to do safely:

  • detect-shoelace-usage — scans the target project and reports every Shoelace element, token, event, and TypeScript class/type reference found, with file/line references. Writes nothing. Needs to look beyond string grepping for the TypeScript layer below (a class MyButton extends SlButton won’t contain the string sl- anywhere). Always run first, and the other scripts should be able to consume its output rather than re-scanning.
  • rewrite-elements — renames custom element tags (<sl-button><cw-button>, opening and closing tags) across plain HTML, JSX/TSX, Vue SFC templates, Angular templates, and Svelte files.
  • rewrite-tokens — renames CSS custom properties (--sl-color-primary-600--cw-color-primary-600, etc.) across CSS/SCSS/LESS files, inline style attributes, and CSS-in-JS.
  • rewrite-events — renames custom event names (sl-changecw-change) in addEventListener calls and framework event bindings (@sl-change in Vue, (slChange) in Angular, onSlChange props in the React wrapper). Easy to miss since it’s neither a tag nor a CSS token — its own namespace, so its own script.
  • rewrite-typescript-refs — renames Shoelace-exported class/type identifiers (SlButtonCwButton, SlChangeEventCwChangeEvent, etc.) everywhere they’re referenced, not just imported — extends clauses on consumers’ own composite components, type annotations, generic constraints, and the ambient HTMLElementTagNameMap/JSX.IntrinsicElements global augmentation these libraries ship (a paired rename: the string key and the type value both change together, or the map ends up mismatched). Covers .ts, .tsx, and .d.ts files. This is the layer that matters most for anyone who’s built their own components on top of Shoelace’s, rather than just used Shoelace’s directly in markup.
  • rewrite-imports — updates JS/TS import specifiers (module paths), CDN <script>/<link> URLs, and framework wrapper package imports. Identifier renaming itself lives in rewrite-typescript-refs above, since that needs whole-file/whole-project reference tracing anyway, not just the import line.
  • update-package-json — swaps the @shoelace-style/shoelace dependency for @cordwainer/cw-elements at the target version.
  • migrate — orchestrator that runs the scripts above in order, supports --dry-run, and prints a summary of what would change or did change.

Implementation note: naive find-and-replace is unsafe here — sl-button and sl-button-group need to be told apart, and a closing tag has to match its opener. Each script should use a real parser for its domain instead of regex: an HTML/template parser (e.g. parse5) for rewrite-elements, PostCSS for rewrite-tokens, and a type-aware AST tool (e.g. ts-morph, using the actual TypeScript language service rather than syntax-only parsing) for rewrite-events and rewrite-typescript-refs — the latter especially, since a text-only replace of SlButton could touch a comment or string literal that shouldn’t change, and won’t reliably catch every real reference the way symbol resolution will.

Distribution — open question

Needs a decision before scoping begins: does migrate ship as a bin command inside @cordwainer/cw-elements itself (npx cw-elements migrate <path>), as its own package (@cordwainer/migrate-from-shoelace), or start as an internal npm run migrate -- <path> script in this repo — good enough to validate against the fixture suite (and a real-world project, once the TODO above turns one up) first — before deciding whether it’s worth productizing for external consumers?

v1.5 — Data Display, Continued & Interaction Primitives

The remaining Data Display components originally scoped for v1.2, plus the general-purpose interaction primitives originally scoped for v1.3, land here once v1.3’s navigation work and v1.4’s migration tooling ship.

<cw-accordion> and <cw-accordion-item>

A multi-panel collapsible group, distinct from the single-panel <cw-details>:

  • Single-expand (“only one panel open”) and multi-expand modes
  • Smooth expand/collapse animation using @cordwainer/cw-animations
  • Same icon/slot conventions as <cw-details> for consistency

<cw-avatar-group>

Stacked, overlapping avatars with an overflow ”+N” indicator, built on the existing <cw-avatar>:

  • Configurable max visible count and overlap amount
  • Overflow indicator can be a <cw-tooltip>-triggered list of remaining members

<cw-stepper> and <cw-stepper-item>

A step indicator for multi-step forms, checkouts, and onboarding flows:

  • Linear and non-linear (jump-to-step) modes
  • Visual states for completed, active, disabled, and error steps
  • Orientation support (horizontal/vertical)

<cw-picture> was also added here originally — it’s moved to the new dedicated Media release below, alongside the audio and video players.

<cw-segmented-button>

A single-select, mutually-exclusive button group — visually similar to <cw-button-group> but behaves like <cw-radio-group> (single selection, form-associated value).

<cw-popover>

A general-purpose popover built on the browser’s native Popover API, complementing the existing <cw-popup> (low-level positioning primitive), <cw-dropdown> (menu-focused), and <cw-tooltip> (hover hints):

  • Anchor positioning reusing @floating-ui/dom (already a dependency)
  • Light-dismiss behavior (click outside / Escape) for free
  • Good fit for rich content like forms or previews that don’t belong in a tooltip or dropdown

Enhance <cw-breadcrumb> to collapse long paths into an ellipsis with an overflow menu, for deeply nested hierarchies (file systems, multi-level categories).

v1.6 — Media

A dedicated release for anything that renders audio, video, or images — pulled out into its own group rather than getting bundled wherever happened to be convenient. None of the four components below are inherited from Shoelace; there’s no equivalent for any of them there, so this is genuinely new ground for the library.

<cw-picture>

A responsive image component wrapping the native <picture>/srcset machinery — a good picture pattern is easy to get wrong by hand:

  • Multiple resolutions via srcset density descriptors (1x/2x/3x) for different device pixel ratios
  • Multiple sizes via width-descriptor srcset plus a sizes attribute, so the browser picks the right image before layout, not after
  • Art direction — different crops or compositions per breakpoint via slotted <source media=""> elements, not just a resized version of the same image
  • Format negotiation — slotted <source type=""> elements so the browser can pick AVIF/WebP with a JPEG/PNG fallback automatically
  • Explicit width/height (or aspect-ratio) required or defaulted, to reserve layout space and avoid content shift while loading
  • Lazy loading passthrough (loading="lazy"), plus an optional placeholder (blurred low-quality image or solid color) shown until the real image loads
  • Decorative-image handling (alt="") kept distinct from meaningful alt text, so accessibility isn’t an afterthought

Open scoping question: purely declarative attributes (src/srcset/sizes/aspect-ratio) for the common case, or also accept slotted <source> elements for full art-direction/format-negotiation control the way native <picture> does? Image-heavy sites usually need both — simple attributes for most images, full <source> control for hero/marketing images — worth deciding which is the v1 baseline before implementation starts.

<cw-audio-player>

A player for self-hosted audio, podcast episodes in particular — inspired by Red Hat Design System’s rh-audio-player, which wraps a real <audio> element with custom controls rather than replacing it:

  • Three sizes (full, compact, mini) differing only in how much extra content is visible — full-size needs a wide viewport; compact/mini work at any breakpoint and can anchor to the top or bottom of a page as a persistent bar
  • Playback controls, seek timeline, and volume, all ARIA-labeled and keyboard-operable
  • Expandable panels for supplementary content: an “about the episode” panel, a “subscribe” panel linking to podcast platforms, and a synced transcript that auto-scrolls and highlights words as they’re spoken
  • Poster/artwork image slot

Open scoping question: transcript-sync (word-level highlighting tied to playback position) is the most technically demanding piece here — it needs either pre-timestamped caption data (WebVTT cues) or a forced-alignment step to generate timestamps from a plain transcript. Worth deciding up front whether v1 requires the consumer to supply timestamped data, or ships without transcript sync and adds it later.

<cw-video-player>

A real player for self-hosted video — not a third-party embed facade. Shares its core engine (play/pause/seek/volume/keyboard handling) with <cw-audio-player>, since both wrap the same underlying HTMLMediaElement API, but ships as a separate element rather than one component with a mode switch — video’s visual chrome genuinely diverges from audio’s (a visible frame, fullscreen, picture-in-picture, on-screen captions), and audio’s compact/mini page-anchored bar pattern doesn’t apply to video:

  • Native <video> wrapping, poster image, fullscreen and picture-in-picture toggles
  • Caption/subtitle track selection (<track kind="captions">), rendered on-screen rather than in a side transcript
  • Shares the about/subscribe/transcript panel components with <cw-audio-player> where they make sense for video content too

Open scoping question — the big one: self-hosting without a platform like YouTube or Vimeo means no adaptive bitrate streaming out of the box. <video> alone just plays one fixed-quality file, which is a poor experience on a slow connection. Real adaptive delivery means integrating something like hls.js or dash.js to feed an HLS/DASH manifest into Media Source Extensions — a meaningfully bigger scope than the player UI itself, and partly a hosting-side problem (manifest generation) outside cw-elements entirely. Needs an explicit decision before implementation starts: ship v1 as single-quality-file playback only, or commit to adaptive streaming from the start.

<cw-video-embed>

Confirmed for this release, alongside the self-hosted <cw-video-player> above — most sites want both at different times, not just self-hosted video. This is a lighter, facade-pattern component for embedding third-party video (YouTube, Vimeo, etc.), genuinely different from <cw-video-player> rather than a smaller version of it: it wraps someone else’s player via iframe and can’t touch its internals, so all it can offer is lazy-loading, not custom controls.

  • Lazy-load facade: a static thumbnail image displays until the user clicks, at which point the real embed (an iframe) activates — avoids loading a third-party player’s JS and tracking scripts until someone actually wants to watch, inspired by the pattern Red Hat Design System’s rh-video-embed uses
  • Works generically with any iframe-embeddable provider rather than baking in provider-specific logic — the embed markup itself is supplied by the consumer (e.g., inside an inert <template>, activated on click)
  • Optional caption slot below the embed

v1.7 — Performance, Theming & Polish

A consolidation release: make all previously-shipped components production-ready at scale, and address longstanding theming gaps.

Virtualized rendering

Opt-in virtual scrolling for components that can hold large datasets:

  • <cw-select> (long option lists)
  • <cw-tree> (large hierarchies)
  • <cw-data-table> (the deferred item from v1.2)

<cw-file-upload>

A drag-and-drop file input with a styled dropzone, file list, and per-file progress using the existing <cw-progress-bar>.

Theming improvements

  • Theme builder tooling/docs to make custom themes easier to author (today there’s only light.css / dark.css / _utility.css)
  • Evaluate a high-contrast theme for accessibility

Accessibility & RTL audit

A focused pass across every component introduced so far: screen reader testing, keyboard navigation review, and right-to-left layout verification.

Beyond v1.7

Ideas that are on our radar but not yet scheduled into a specific release:

  • Drag-and-drop sorting utilities (for <cw-tree>, lists, and the data table)
  • Lightweight data-visualization primitives (sparklines, simple charts)
  • Additional framework integrations beyond React/Vue/Angular/Svelte (e.g., SolidJS, Qwik)

Have thoughts on sequencing, or a use case that needs something sooner? Open a work item — community input directly shapes how this roadmap evolves.