A long-lived project that still receives updates
An open source Rails 8+ engine that provides WCAG 2.2 AA compliant design tokens, Tailwind CSS utilities, and Stimulus controllers for theme switching, mobile navigation, slide-out panels, modals, and tabs. Ships as pure frontend with no server-side dependencies beyond Rails and Tailwind CSS.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 5.0
~> 43.2
~> 1.0
~> 7.0
~> 2.0

Runtime

 Project Readme

layered-ui-rails

CI WCAG 2.2 AA License: Apache 2.0 Website GitHub Discord

An open source, Rails 8+ engine that provides WCAG 2.2 AA compliant design tokens, Tailwind CSS utilities, and Stimulus controllers for theme switching, mobile navigation, slide-out panels, modals, and tabs. Ships as pure frontend with no server-side dependencies beyond Rails and Tailwind CSS. See the live demo.

Light theme (desktop) Light theme (mobile) Dark theme (desktop) Dark theme (mobile)
Light theme (desktop and mobile) Dark theme (desktop and mobile)

Getting started

Add to your Gemfile and install:

bundle add layered-ui-rails
bin/rails generate layered:ui:install

The install generator will:

  • Copy layered_ui.css to app/assets/tailwind/
  • Add @import "./layered_ui"; to your application.css
  • Add import "layered_ui" to your application.js

Then update your application layout to render the engine layout:

<%= render template: "layouts/layered_ui/application" %>

Requirements

  • Ruby on Rails >= 8.0
  • Tailwind CSS Rails >= 4.0
  • Importmap Rails >= 2.0
  • Stimulus Rails >= 1.0

Features

  • Dark/light theme - system preference detection with localStorage persistence and manual toggle
  • Responsive layout - header, sidebar navigation, main content area, and optional resizable panel
  • WCAG 2.2 AA compliant - skip links, focus indicators, ARIA attributes, and 4.5:1 contrast ratios
  • Components - buttons, forms, surfaces, tables, tabs, notices, badges, conversations, modals, and pagination
  • Optional integrations - Devise authentication and Pagy pagination with styled views
  • Customisable branding - Override the default logos and icons and colors
  • Google Lighthouse - layered-ui-rails scores a perfect 100 across all four Google Lighthouse categories - performance, accessibility, best practices, and SEO

Customising theme tokens

All colors are CSS custom properties on :root. Override any token in your stylesheet (after importing the engine CSS):

/* app/assets/tailwind/application.css */
@import "./layered_ui";

:root {
  --accent: 220 80% 55%;
  --accent-foreground: 0 0% 100%;
}

.dark {
  --accent: 220 80% 65%;
  --accent-foreground: 0 0% 9%;
}

For dynamic theming (e.g. per-tenant branding), use content_for :l_ui_head to inject content into the layout <head>:

<% content_for :l_ui_head do %>
  <style>
    :root { --accent: <%= @tenant.accent_hsl %>; --accent-foreground: 0 0% 100%; }
  </style>
<% end %>

Security: never interpolate user-supplied strings directly into a <style> tag - this allows CSS injection (Important: Validate or sanitise any user-derived values before interpolation).

CSP compatibility: inline <style> blocks are blocked by a strict Content-Security-Policy: style-src 'self' header. If your app enforces a strict CSP, add a nonce to the style tag using Rails' content_security_policy_nonce helper - Rails automatically includes the matching nonce in the CSP header:

<% content_for :l_ui_head do %>
  <style nonce="<%= content_security_policy_nonce %>">
    :root { --accent: <%= @tenant.accent_hsl %>; --accent-foreground: 0 0% 100%; }
  </style>
<% end %>

See the Colors documentation for the full list of tokens.

Customising logos and icons

Replace the defaults by placing files with the same names in app/assets/images/layered_ui/ in your host app:

File Used for
logo_light.svg Header logo (light theme)
logo_dark.svg Header logo (dark theme)
icon_light.svg Favicon and header icon (light theme)
icon_dark.svg Favicon and header icon (dark theme)
apple_touch_icon.png Apple touch icon
panel_icon_light.svg Panel toggle button (light theme)
panel_icon_dark.svg Panel toggle button (dark theme)

layered-ui-rails uses two patterns for per-request overrides:

  • Instance variables (@l_ui_*) - used for small changes like URLs. The engine renders the surrounding markup and just swaps the src/href.
  • content_for - used when the full markup needs to change. You supply the complete tag, so you can set classes, attributes, or wrap elements as needed.

For per-request logos (e.g. per-tenant branding), use content_for because the <img> tag itself carries classes that control layout and theme switching:

<% content_for :l_ui_logo_light do %>
  <%= image_tag @tenant.logo_light_url, alt: "", class: "l-ui-header__logo l-ui-header__logo--light" %>
<% end %>

<% content_for :l_ui_logo_dark do %>
  <%= image_tag @tenant.logo_dark_url, alt: "", class: "l-ui-header__logo l-ui-header__logo--dark" %>
<% end %>

To adjust the size of the logo or header icon, override .l-ui-header__logo or .l-ui-header__icon in layered_ui_overrides.css (created by bin/rails generate layered:ui:create_overrides). Commented examples are included in the Tier 3 section of that file.

For per-request icons, set instance variables - the engine renders <link> and <img> tags that only need a URL to vary:

@l_ui_icon_light_url = @tenant.icon_light_url
@l_ui_icon_dark_url = @tenant.icon_dark_url
@l_ui_apple_touch_icon_url = @tenant.apple_touch_icon_url
@l_ui_panel_icon_light_url = @tenant.panel_icon_light_url
@l_ui_panel_icon_dark_url = @tenant.panel_icon_dark_url

Security: Rails HTML-escapes URL values, so XSS via attribute injection is mitigated. However, if values are tenant-controlled, validate that they are legitimate URLs - reject javascript: schemes and ensure values point to expected origins.

Documentation

An online version of the documentation is available at layered-ui-rails.layered.ai.

The latest accessibility audit is available at audits/accessibility/codex-5_3.md.

You can also run the included dummy app locally for development and testing:

git clone https://github.com/layered-ai-public/layered-ui-rails.git
cd layered-ui-rails
bundle install
cd test/dummy && bin/rails db:setup && bin/dev

The dummy app serves as both a living style guide and a test harness, with interactive examples and code snippets for every component.

Deploying the dummy app

The dummy app can be deployed with Kamal. Set the required environment variables and deploy from test/dummy:

cd test/dummy
export KAMAL_DEPLOY_IP=<server-ip>
export KAMAL_DEPLOY_DOMAIN=<domain>
export KAMAL_SSH_KEY=<path-to-ssh-key>
kamal deploy

Contributing

This project is still in its early days. We welcome issues, feedback, and ideas - they genuinely help shape the direction of the project. That said, we're holding off on accepting pull requests until after the 1.0 release so we can stay focused on getting the core foundations right. Once we're there, we'd love to open things up to broader contributions. Thanks for your patience and interest!

License

Released under the Apache 2.0 License.

Copyright 2026 LAYERED AI LIMITED (UK company number: 17056830). See NOTICE for attribution details.

Trademarks

The source code is fully open, but the layered.ai name, logo, and brand assets are trademarks of LAYERED AI LIMITED. The Apache 2.0 license does not grant rights to use the layered.ai branding. Forks and redistributions must use a distinct name. See TRADEMARK.md for the full policy.

Contributing

  • CLA.md - contributor license agreement