Jede Rails-App ab einer gewissen Grösse leidet unter dem gleichen Problem: Die UI wird inkonsistent. Buttons sehen auf jeder Seite anders aus, Formulare verhalten sich unterschiedlich, und niemand weiss mehr, welches Partial die “richtige” Version ist. Ein Design-System löst das, aber in Rails gibt es mehrere Wege dorthin. Dieser Artikel zeigt, welcher Ansatz in der Praxis funktioniert.
Warum Partials für Design-Systeme nicht reichen
Rails-Partials sind der Standardweg für wiederverwendbare UI-Elemente. Für einfache Projekte funktioniert das. Sobald eine App aber 50+ Views hat, zeigen sich die Schwächen:
- Kein Interface-Vertrag: Partials akzeptieren beliebige Locals ohne Validierung. Ein fehlendes
locals-Argument fällt erst zur Laufzeit auf. - Kein isolierter Test: Partials lassen sich nur über Integration Tests prüfen. Unit Tests für einzelne UI-Elemente sind nicht vorgesehen.
- Implicit Dependencies: Partials greifen oft auf Instanzvariablen zu, die der Controller setzt. Das macht Refactoring riskant.
- Keine Preview-Möglichkeit: Ohne den passenden Controller-Context lässt sich ein Partial nicht isoliert betrachten.
Diese Probleme wachsen mit der Teamgrösse. In einer App mit 5+ Entwicklern führt das schnell zu doppelten Implementierungen und inkonsistenter UI.
ViewComponent als Basis: Das steckt dahinter
ViewComponent von GitHub löst die zentralen Partial-Probleme durch echte Ruby-Objekte mit klarem Interface:
# app/components/button_component.rb
class ButtonComponent < ViewComponent::Base
VARIANTS = %i[primary secondary danger ghost].freeze
SIZES = %i[sm md lg].freeze
erb_template <<~ERB
<button class="<%= classes %>" <%= html_attributes %>>
<%= content %>
</button>
ERB
def initialize(variant: :primary, size: :md, **html_attrs)
raise ArgumentError, "Unknown variant: #{variant}" unless VARIANTS.include?(variant)
raise ArgumentError, "Unknown size: #{size}" unless SIZES.include?(size)
@variant = variant
@size = size
@html_attrs = html_attrs
end
private
def classes
"btn btn--#{@variant} btn--#{@size}"
end
def html_attributes
@html_attrs.map { |k, v| "#{k}=\"#{v}\"" }.join(" ").html_safe
end
end
Der entscheidende Unterschied zu Partials:
| Eigenschaft | Partial | ViewComponent |
|---|---|---|
| Typsicherheit | Keine (beliebige Locals) | Ruby-Konstruktor mit Validierung |
| Unit Tests | Nicht vorgesehen | render_inline im Test |
| Preview | Nur via Controller | Lookbook / ViewComponent::Preview |
| Performance | 1x (Baseline) | ~10x schneller (GitHub-Benchmark) |
| Slot-System | Nicht vorhanden | Content Slots für komplexe Layouts |
GitHub selbst hat 2023 über 200 UI-Komponenten auf ViewComponent migriert und berichtet von signifikant weniger UI-Inkonsistenzen.
Lookbook: Der fehlende Style Guide für Rails
Ein Design-System ohne visuelle Dokumentation ist nur eine Komponentenbibliothek. Lookbook schliesst diese Lücke für Rails.
Lookbook generiert automatisch einen interaktiven Style Guide aus ViewComponent-Previews:
# test/components/previews/button_component_preview.rb
class ButtonComponentPreview < Lookbook::Preview
# @param variant select { choices: [primary, secondary, danger, ghost] }
# @param size select { choices: [sm, md, lg] }
def default(variant: :primary, size: :md)
render ButtonComponent.new(variant: variant.to_sym, size: size.to_sym) do
"Klick mich"
end
end
def all_variants
render_with_template
end
end
Damit erhalten Designer und Entwickler eine gemeinsame Referenz unter /lookbook im Development-Modus. Jede Komponente ist dort interaktiv testbar, mit Parametern, die sich live anpassen lassen.
Setup in 3 Minuten:
bundle add lookbook
# config/routes.rb (nur development)
mount Lookbook::Engine, at: "/lookbook" if Rails.env.development?
Für Teams ab 3 Personen ist Lookbook kein Nice-to-have, sondern spart messbar Zeit bei Design-Reviews und Onboarding.
Rails 8 + KI + Tailwind Das Ultimative Designsystem-Tutorial 2024
Tailwind CSS oder eigene Design Tokens?
Die Frage “Tailwind oder Custom CSS?” stellt sich in jedem Rails-Projekt. Unsere Erfahrung nach über 15 Jahren Rails-Entwicklung:
Tailwind funktioniert gut, wenn:
- Das Team >3 Personen hat und einheitliches Styling braucht
- Schnelle Prototypen gefragt sind
- Die App kein stark individuelles visuelles Design benötigt
Custom Design Tokens sind besser, wenn:
- Das Branding komplex ist (mehrere Produkte, White-Label)
- Performance kritisch ist (Tailwind’s Utility-Klassen erzeugen grössere HTML-Dateien)
- Das Team CSS-Expertise hat
In der Praxis kombinieren wir bei USEO oft beides: Tailwind für Layout und Spacing, Custom Properties für Farben und Typografie.
// tailwind.config.js - Design Tokens als Tailwind-Theme
module.exports = {
content: [
'./app/views/**/*.html.erb',
'./app/components/**/*.{rb,html.erb}',
'./app/helpers/**/*.rb'
],
theme: {
extend: {
colors: {
brand: {
DEFAULT: 'var(--color-brand)',
light: 'var(--color-brand-light)',
dark: 'var(--color-brand-dark)',
}
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif']
}
}
}
}
So bleiben die Tailwind-Utility-Klassen konsistent, aber die konkreten Farbwerte lassen sich zentral via CSS Custom Properties steuern.
USEO’s Take: Unser Design-System-Ansatz in Rails
Nach hunderten Rails-Projekten haben wir bei USEO einen klaren Ansatz entwickelt:
1. ViewComponent ab Tag 1
Wir starten jedes neue Projekt mit ViewComponent. Nicht weil Partials schlecht sind, sondern weil der Wechsel später teuer wird. Eine Migration von 80+ Partials zu ViewComponent hat uns in einem Kundenprojekt ca. 3 Wochen gekostet. Der gleiche Aufwand am Anfang: 2 Tage Setup.
2. Lookbook als lebende Dokumentation
Figma-Dateien veralten. Lookbook nicht, weil es direkt aus dem Code generiert wird. Wir nutzen es als Single Source of Truth für UI-Entscheidungen.
3. Komponentenhierarchie mit Atomic Design
Wir strukturieren ViewComponents in drei Ebenen:
app/components/
├── atoms/ # ButtonComponent, BadgeComponent, IconComponent
├── molecules/ # FormFieldComponent, CardComponent, NavItemComponent
└── organisms/ # HeaderComponent, SidebarComponent, DataTableComponent
Atome haben keine Abhängigkeiten auf andere Komponenten. Moleküle kombinieren Atome. Organismen kombinieren Moleküle. Diese Regel wird im Code Review durchgesetzt.
4. Visuelle Regressionstests mit Percy oder Capybara-Screenshot
# spec/components/button_component_spec.rb
RSpec.describe ButtonComponent, type: :component do
it "renders primary variant" do
render_inline(described_class.new(variant: :primary)) { "Submit" }
expect(page).to have_css("button.btn.btn--primary")
expect(page).to have_text("Submit")
end
it "rejects invalid variants" do
expect {
render_inline(described_class.new(variant: :invalid)) { "Test" }
}.to raise_error(ArgumentError)
end
end
Unit Tests für jede Komponente sind Pflicht. Dazu kommen visuelle Snapshot-Tests, die Layout-Regressionen automatisch erkennen.
Hotwire und Turbo: Interaktivität ohne Chaos
Ein Design-System muss auch dynamische UI abdecken. Hotwire mit Turbo Frames und Turbo Streams integriert sich nahtlos mit ViewComponent:
<%# app/views/tasks/index.html.erb %>
<%= turbo_frame_tag "task_list" do %>
<% @tasks.each do |task| %>
<%= render TaskCardComponent.new(task: task) %>
<% end %>
<% end %>
Turbo Streams aktualisieren einzelne Komponenten, ohne die gesamte Seite neu zu laden. Die UI-Logik bleibt dabei serverseitig in den ViewComponents, statt in JavaScript-Fragmenten verstreut zu sein.
Der Vorteil gegenüber React oder Vue: Kein zweites Komponentensystem. Die gleichen ViewComponents rendern sowohl die initiale Seite als auch dynamische Updates.
Legacy-Rails-App modernisieren: Schritt für Schritt
Die meisten Rails-Apps haben kein Design-System, sondern gewachsenes CSS und hunderte Partials. Eine Migration auf ViewComponent funktioniert am besten inkrementell:
Phase 1: Audit (1-2 Tage)
Inventar der bestehenden UI-Elemente erstellen. Häufig finden sich 5+ Varianten des gleichen Buttons.
Phase 2: Foundation (1 Woche)
Design Tokens definieren, ViewComponent und Lookbook einrichten, 5-10 Basis-Komponenten bauen (Button, Input, Card, Badge, Alert).
Phase 3: Migration (laufend)
Bei jedem Feature oder Bugfix die betroffenen Partials durch ViewComponents ersetzen. Kein Big Bang, sondern organische Migration.
Phase 4: Enforcement
Linter-Regel einführen, die neue Partials in Verzeichnissen flaggt, wo bereits ViewComponents existieren. RuboCop Custom Cops eignen sich dafür.
Bei USEO haben wir diesen Prozess für mehrere Kunden durchgeführt, auch für Schweizer Unternehmen mit mehrsprachigen Anforderungen (DE, FR, IT). Die Ergebnisse sind konsistent: weniger UI-Bugs, schnelleres Onboarding neuer Entwickler, und messbar kürzere Feature-Zyklen.
Zugänglichkeit von Anfang an einbauen
Ein Design-System ist der ideale Ort, um Barrierefreiheit zentral zu verankern. Statt ARIA-Attribute in jedem View einzeln zu setzen, werden sie Teil der Komponente:
class AlertComponent < ViewComponent::Base
ROLES = { info: "status", warning: "alert", error: "alert" }.freeze
def initialize(type: :info)
@type = type
end
def role
ROLES.fetch(@type)
end
def aria_live
@type == :error ? "assertive" : "polite"
end
end
<div role="<%= role %>" aria-live="<%= aria_live %>" class="alert alert--<%= @type %>">
<%= content %>
</div>
Zugänglichkeit wird so zum Default, nicht zur Ausnahme. Neue Teammitglieder können keine unzugänglichen Alerts bauen, weil die Komponente es nicht zulässt.
Toolchain-Übersicht
| Tool | Zweck | Gem / Package | Empfehlung |
|---|---|---|---|
| ViewComponent | UI-Komponenten mit Ruby-Interface | view_component | Pflicht |
| Lookbook | Interaktiver Style Guide | lookbook | Pflicht ab 3+ Devs |
| Tailwind CSS | Utility-first Styling | tailwindcss-rails | Empfohlen |
| Primer ViewComponents | Fertige, zugängliche Komponenten | primer_view_components | Optional (GitHub-spezifisch) |
| Percy | Visuelle Regressionstests | Percy CI | Empfohlen für grössere Apps |
| RuboCop | Linting für Komponentenregeln | Custom Cops | Empfohlen |
Fazit
Design-Systeme in Rails sind kein abstraktes Konzept. Mit ViewComponent, Lookbook und Tailwind gibt es ein ausgereiftes Tooling, das sich in der Praxis bewährt hat. Der wichtigste Schritt: Früh anfangen. Je länger eine App ohne Design-System wächst, desto teurer wird die Migration.
Wer eine bestehende Rails-App modernisieren will oder ein neues Projekt von Anfang an richtig aufsetzen möchte, kann sich an USEO wenden. Wir sind auf Ruby on Rails spezialisiert und haben diesen Prozess dutzende Male durchgeführt.
FAQs
Wann lohnt sich der Umstieg von Partials auf ViewComponent?
Ab dem Moment, wo mehrere Entwickler an der gleichen App arbeiten oder die Zahl der Partials unübersichtlich wird (typisch ab 30-50 Partials). Der Aufwand für die initiale Einrichtung ist gering (1-2 Tage), die langfristigen Vorteile durch testbare, typisierte Komponenten aber erheblich.
Ist Primer ViewComponents für Nicht-GitHub-Projekte sinnvoll?
Bedingt. Primer bringt GitHubs Design-Sprache mit, was für interne Tools gut passt. Für kundenspezifische Produkte mit eigenem Branding empfehlen wir eigene ViewComponents mit Tailwind. So behalten Sie volle Kontrolle über das visuelle Design.
Wie halte ich das Design-System aktuell?
Drei Massnahmen: (1) Lookbook als Single Source of Truth, (2) visuelle Regressionstests im CI, (3) Code-Review-Regel, dass neue UI-Elemente als ViewComponent gebaut werden müssen. Ohne diese Disziplin veraltet jedes Design-System innerhalb von 6 Monaten.