
Company News
Free Business Plan Upgrades for Open Source Maintainers
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.
@ios27_design_system/rails
Advanced tools
iOS 27 design system for Rails 8 + Hotwire — CSS tokens, Stimulus controllers, ERB partials
Production-ready implementation of the iOS 27 design language for Rails 8 applications using Stimulus, ERB, and Turbo.
app/
assets/
stylesheets/
ios27/
tokens.css <-- Design tokens (colors, typography, materials)
sheet.css <-- Sheet component styles
context-menu.css <-- Context menu styles
javascript/
controllers/
ios27_toolbar_controller.js
ios27_tab_bar_controller.js
ios27_sheet_controller.js
ios27_alert_controller.js
ios27_context_menu_controller.js
ios27_dark_mode_controller.js
views/
shared/
ios27/
_toolbar.html.erb
_list_row.html.erb
_list_section.html.erb
_alert.html.erb
_button.html.erb
_tab_bar.html.erb
Propshaft (Rails 8 default):
/* app/assets/stylesheets/application.css */
/* Add to your existing stylesheet */
@import "ios27/tokens.css";
@import "ios27/sheet.css";
@import "ios27/context-menu.css";
Sprockets (legacy):
/* app/assets/stylesheets/application.css */
/*
*= require ios27/tokens
*= require ios27/sheet
*= require ios27/context-menu
*/
Option A: Bulk registration (recommended)
// app/javascript/controllers/index.js
import { application } from "./application"
import { registerIos27Controllers } from "./ios27_controller"
registerIos27Controllers(application)
Option B: Individual registration
// app/javascript/controllers/index.js
import { application } from "./application"
import { Ios27ToolbarController } from "./ios27_controller"
import { Ios27TabBarController } from "./ios27_controller"
import { Ios27SheetController } from "./ios27_controller"
import { Ios27AlertController } from "./ios27_controller"
import { Ios27ContextMenuController } from "./ios27_controller"
import { Ios27DarkModeController } from "./ios27_controller"
application.register("ios27-toolbar", Ios27ToolbarController)
application.register("ios27-tab-bar", Ios27TabBarController)
application.register("ios27-sheet", Ios27SheetController)
application.register("ios27-alert", Ios27AlertController)
application.register("ios27-context-menu", Ios27ContextMenuController)
application.register("ios27-dark-mode", Ios27DarkModeController)
# config/importmap.rb
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin_all_from "app/javascript/controllers", under: "controllers"
A complete Rails view demonstrating the iOS 27 design system in action.
<%# app/views/settings/index.html.erb %>
<% content_for :title, "Settings" %>
<%# -- Toolbar with large title -- %>
<%= render "shared/ios27/toolbar",
title: "Settings",
large_title: true %>
<%# -- Main scrollable content -- %>
<main style="background: var(--ios-bg-grouped-primary);
padding: var(--ios-spacing-lg) 0;
padding-bottom: calc(49px + var(--ios-safe-area-bottom) + var(--ios-spacing-2xl));
min-height: 100vh;"
data-ios27-toolbar-target="scrollContainer">
<%# -- Profile Section -- %>
<%= render "shared/ios27/list_section" do %>
<%= render "shared/ios27/list_row",
title: current_user.name,
subtitle: "Apple Account, iCloud, and purchases",
leading: '<div style="width:44px;height:44px;border-radius:50%;background:var(--ios-color-blue);display:flex;align-items:center;justify-content:center;color:white;font-weight:600;font-size:18px;">SH</div>',
trailing: '<span class="ios-row__chevron"></span>',
url: account_path %>
<% end %>
<%# -- Connectivity Section -- %>
<%= render "shared/ios27/list_section" do %>
<%= render "shared/ios27/list_row",
title: "Wi-Fi",
detail: "Home-5G",
leading: '<div style="background:var(--ios-color-blue);width:29px;height:29px;border-radius:6px;display:flex;align-items:center;justify-content:center;color:white;font-size:14px;">W</div>',
trailing: '<span class="ios-row__chevron"></span>',
url: wifi_settings_path %>
<%= render "shared/ios27/list_row",
title: "Bluetooth",
detail: "On",
leading: '<div style="background:var(--ios-color-blue);width:29px;height:29px;border-radius:6px;display:flex;align-items:center;justify-content:center;color:white;font-size:14px;">B</div>',
trailing: '<span class="ios-row__chevron"></span>',
url: bluetooth_settings_path %>
<%= render "shared/ios27/list_row",
title: "Cellular",
leading: '<div style="background:var(--ios-color-green);width:29px;height:29px;border-radius:6px;display:flex;align-items:center;justify-content:center;color:white;font-size:14px;">C</div>',
trailing: '<span class="ios-row__chevron"></span>',
url: cellular_settings_path %>
<% end %>
<%# -- Notifications (lazy loaded via Turbo Frame) -- %>
<%= render "shared/ios27/list_section", header: "NOTIFICATIONS" do %>
<turbo-frame id="notification-prefs" src="<%= notification_preferences_path %>" loading="lazy">
<div class="ios-row" style="justify-content:center;min-height:88px;">
<span class="ios-footnote" style="color:var(--ios-label-tertiary);">Loading...</span>
</div>
</turbo-frame>
<% end %>
<%# -- Danger Zone -- %>
<%= render "shared/ios27/list_section", footer: "Signing out will remove your data from this device." do %>
<%= render "shared/ios27/list_row",
title: "Sign Out",
destructive: true,
url: sign_out_confirm_path %>
<% end %>
</main>
<%# -- Tab Bar -- %>
<%= render "shared/ios27/tab_bar",
current: "settings",
tabs: [
{ id: "home", label: "Home", url: root_path,
icon_html: '<svg width="25" height="25" viewBox="0 0 25 25"><path d="M3 10.5L12.5 3L22 10.5V21H15.5V15H9.5V21H3V10.5Z" fill="currentColor"/></svg>' },
{ id: "search", label: "Search", url: search_path,
icon_html: '<svg width="25" height="25" viewBox="0 0 25 25"><circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="2" fill="none"/><line x1="16" y1="16" x2="22" y2="22" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>' },
{ id: "settings", label: "Settings", url: settings_path,
icon_html: '<svg width="25" height="25" viewBox="0 0 25 25"><circle cx="12.5" cy="12.5" r="3" stroke="currentColor" stroke-width="2" fill="none"/><path d="M12.5 2L14 5H11L12.5 2Z" fill="currentColor"/></svg>' }
] %>
<%# -- Dark mode toggle (optional) -- %>
<div style="position:fixed;top:var(--ios-spacing-sm);right:var(--ios-spacing-sm);z-index:200;">
<button data-controller="ios27-dark-mode"
data-action="ios27-dark-mode#toggle"
class="ios-btn ios-btn--liquid_glass ios-btn--small">
Dark Mode
</button>
</div>
<%# app/views/layouts/application.html.erb %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<title><%= yield(:title) || "App" %></title>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body style="font-family: var(--ios-font-family);
color: var(--ios-label-primary);
background: var(--ios-bg-grouped-primary);
margin: 0;
-webkit-font-smoothing: antialiased;">
<%# Alerts and sheets render into this container via Turbo Streams %>
<div id="modal-container"></div>
<%= yield %>
</body>
</html>
| File | Purpose |
|---|---|
tokens.css | All 50+ CSS custom properties: colors, typography, materials, spacing, animation |
_ios27_helpers.html.erb | ERB partials: toolbar, list_row, list_section, alert, button, tab_bar |
ios27_controller.js | 6 Stimulus controllers: toolbar, tab-bar, sheet, alert, context-menu, dark-mode |
components.md | Component mapping guide with ERB code examples |
README.md | This file -- setup and usage guide |
All tokens use the --ios- prefix. Key categories:
| Category | Example Variable | Light Value |
|---|---|---|
| System Colors | --ios-color-blue | #0088ff |
| Labels | --ios-label-primary | rgba(0,0,0,1) |
| Backgrounds | --ios-bg-grouped-primary | #f2f2f7 |
| Fills | --ios-fill-primary | rgba(120,120,120,0.2) |
| Grays | --ios-gray | #8e8e93 |
| Glass | --ios-glass-bg-medium | rgba(245,245,245,0.6) |
| Typography | --ios-body-size | 17px |
| Spacing | --ios-spacing-md | 16px |
| Radius | --ios-radius-md | 12px |
| Animation | --ios-duration-normal | 250ms |
Dark mode values override automatically via @media (prefers-color-scheme: dark) or manually via the .ios27-dark class on <html>.
Default behavior. All <a> tags get SPA-like navigation. Toolbar and tab bar persist.
Use for tab content, lazy-loaded sections, and inline editing.
<turbo-frame id="content">
<%# Only this frame updates on navigation %>
</turbo-frame>
Use for alerts, notifications, and server-pushed changes.
# Controller
respond_to do |format|
format.turbo_stream {
render turbo_stream: turbo_stream.append("modal-container",
partial: "shared/ios27/alert",
locals: { title: "Done!", message: "Item saved.", actions: [{ label: "OK", variant: :cancel }] }
)
}
end
backdrop-filter, :has(), env())The Liquid Glass effect (backdrop-filter: blur()) degrades gracefully to a solid background on unsupported browsers.
FAQs
iOS 27 design system for Rails 8 + Hotwire — CSS tokens, Stimulus controllers, ERB partials
The npm package @ios27_design_system/rails receives a total of 16 weekly downloads. As such, @ios27_design_system/rails popularity was classified as not popular.
We found that @ios27_design_system/rails demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.