Previews

No matching results.

x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
<div class="midwest-layout size-lg padding-default type-supporting-content" style="--layout-grid-gap: 3rem; --layout-sidebar-width: 350px">
<div class="wrapper">
<section class="min-w-0">
<nav class="midwest-breadcrumbs style-inline" aria-label="Breadcrumb">
<ol class="breadcrumbs-list">
<li class="breadcrumb-item">
<a class="midwest-breadcrumb variant-default" href="#">
<span class="midwest-label ">
Blog
</span>
</a>
</li>
<li class="breadcrumb-separator" aria-hidden="true">
<span class="sep-char" aria-hidden="true">/</span>
</li>
<li class="breadcrumb-item">
<a class="midwest-breadcrumb variant-default" href="#">
<span class="midwest-label ">
Engineering
</span>
</a>
</li>
<li class="breadcrumb-separator" aria-hidden="true">
<span class="sep-char" aria-hidden="true">/</span>
</li>
<li class="breadcrumb-item">
<span class="midwest-breadcrumb variant-default current" aria-current="page">
<span class="midwest-label ">
Design Systems at Scale
</span>
</span>
</li>
</ol>
</nav>
<header class="mt-4 mb-8">
<div class="flex items-center gap-2 mb-3">
<span class="midwest-badge size-default theme-primary">
<span class="value">Design Systems</span>
</span>
<span class="midwest-badge size-default theme-cyan">
<span class="value">Architecture</span>
</span>
</div>
<h1 class="text-3xl font-bold text-theme-12 leading-tight mb-4">
Design Systems at Scale: Lessons from Building Midwest
</h1>
<div class="flex items-center gap-4">
<span class="midwest-avatar size-sm theme-violet shape-square" title="You tabbed on an Avatar for Priya Nair">
<div class="letter" title="Priya Nair">
PN
</div>
</span>
<div class="text-sm text-theme-8">
<span class="font-medium text-theme-11">Priya Nair</span>
· May 14, 2026
· 12 min read
</div>
</div>
</header>
<div data-controller="midwest-prose" class="midwest-prose prose">
<h2>Why design systems fail</h2>
<p>Most design systems start with the best intentions. A handful of components, a shared colour palette, a Figma file that everyone promises to keep updated. Then product pressure hits. Components fork. The Figma file drifts. Engineers start copy-pasting instead of composing.</p>
<p>We went through exactly this cycle before building Midwest — and the experience taught us a handful of patterns that make component libraries actually stick.</p>
<h2>Constraint is a feature</h2>
<p>Every option you expose in a component API is a decision you're pushing to every consumer. A button with twenty-three props isn't flexible — it's undecided. The most durable systems have a small, opinionated surface area and a clear path for one-off extensions.</p>
<blockquote>
<p>The best component is the one you don't have to think about.</p>
</blockquote>
<p>Midwest keeps props minimal by leaning on semantic tokens (states, sizes, variants) rather than exposing raw CSS values. This means consumers stay in the design language rather than escaping it.</p>
<h2>Co-locate styles with components</h2>
<p>Separating component styles into a global stylesheet is a maintenance trap. When the stylesheet is far from the component, both drift. Midwest co-locates a <code>.css</code> file with every component, imported via a single Tailwind entry point. Deleting a component means deleting a folder — no orphan styles left behind.</p>
<h2>Test the contract, not the implementation</h2>
<p>Unit tests for UI components should verify what the component promises to render, not how it renders it internally. We test output HTML and ARIA attributes — things that consumers depend on — and leave CSS class composition to linting. This gives us confidence to refactor internals without breaking consumers.</p>
</div>
<div class="mt-8 pt-6 border-t border-theme-3 flex items-center justify-between">
<a class="midwest-button variant-outline size-default theme-primary" name="button" href="/">
<span class="midwest-button-content">← Previous article</span>
<div class="midwest-loading-icon" role="img" aria-label="Loading" aria-hidden="true" style="display: none">
<div class="midwest-loading-icon-inner">
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg viewbox="0 0 120 30" xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="Midwest-LoadingIcon__Svg" focusable="false" aria-hidden="true">
<circle cx="15" cy="15" r="15">
<animate attributename="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcmode="linear" repeatcount="indefinite"></animate>
<animate attributename="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcmode="linear" repeatcount="indefinite"></animate>
</circle>
<circle cx="60" cy="15" r="9" fill-opacity="0.3">
<animate attributename="r" from="9" to="9" begin="0s" dur="0.8s" values="9;15;9" calcmode="linear" repeatcount="indefinite"></animate>
<animate attributename="fill-opacity" from="0.5" to="0.5" begin="0s" dur="0.8s" values=".5;1;.5" calcmode="linear" repeatcount="indefinite"></animate>
</circle>
<circle cx="105" cy="15" r="15">
<animate attributename="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcmode="linear" repeatcount="indefinite"></animate>
<animate attributename="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcmode="linear" repeatcount="indefinite"></animate>
</circle>
</svg>
</div>
</div>
</a>
<a class="midwest-button variant-default size-default theme-primary" name="button" href="/">
<span class="midwest-button-content">Next article →</span>
<div class="midwest-loading-icon" role="img" aria-label="Loading" aria-hidden="true" style="display: none">
<div class="midwest-loading-icon-inner">
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg viewbox="0 0 120 30" xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="Midwest-LoadingIcon__Svg" focusable="false" aria-hidden="true">
<circle cx="15" cy="15" r="15">
<animate attributename="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcmode="linear" repeatcount="indefinite"></animate>
<animate attributename="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcmode="linear" repeatcount="indefinite"></animate>
</circle>
<circle cx="60" cy="15" r="9" fill-opacity="0.3">
<animate attributename="r" from="9" to="9" begin="0s" dur="0.8s" values="9;15;9" calcmode="linear" repeatcount="indefinite"></animate>
<animate attributename="fill-opacity" from="0.5" to="0.5" begin="0s" dur="0.8s" values=".5;1;.5" calcmode="linear" repeatcount="indefinite"></animate>
</circle>
<circle cx="105" cy="15" r="15">
<animate attributename="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcmode="linear" repeatcount="indefinite"></animate>
<animate attributename="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcmode="linear" repeatcount="indefinite"></animate>
</circle>
</svg>
</div>
</div>
</a>
</div>
</section>
<aside class="space-y-6">
<article data-controller="midwest-card" class="midwest-card padding-sm">
<div class="wrap">
<section>
<p class="text-xs font-semibold text-theme-7 uppercase tracking-wide mb-3">On this page</p>
<nav class="space-y-1">
<a href="#" class="block text-sm py-1 text-theme-9 font-medium">
Why design systems fail
</a>
<a href="#" class="block text-sm py-1 text-theme-7 hover:text-theme-11">
Constraint is a feature
</a>
<a href="#" class="block text-sm py-1 text-theme-7 hover:text-theme-11">
Co-locate styles
</a>
<a href="#" class="block text-sm py-1 text-theme-7 hover:text-theme-11">
Test the contract
</a>
</nav>
</section>
</div>
</article>
<article data-controller="midwest-card" class="midwest-card padding-sm">
<div class="wrap">
<section>
<p class="text-xs font-semibold text-theme-7 uppercase tracking-wide mb-3">Written by</p>
<div class="flex items-start gap-3">
<span class="midwest-avatar size-lg theme-violet shape-square" title="You tabbed on an Avatar for Priya Nair">
<div class="letter" title="Priya Nair">
PN
</div>
</span>
<div>
<p class="text-sm font-semibold text-theme-11">Priya Nair</p>
<p class="text-xs text-theme-7 mt-0.5">Staff Engineer at Unabridged Software</p>
<a class="midwest-button variant-outline size-small theme-primary mt-2" name="button" href="/">
<span class="midwest-button-content">Follow</span>
<div class="midwest-loading-icon" role="img" aria-label="Loading" aria-hidden="true" style="display: none">
<div class="midwest-loading-icon-inner">
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg viewbox="0 0 120 30" xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="Midwest-LoadingIcon__Svg" focusable="false" aria-hidden="true">
<circle cx="15" cy="15" r="15">
<animate attributename="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcmode="linear" repeatcount="indefinite"></animate>
<animate attributename="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcmode="linear" repeatcount="indefinite"></animate>
</circle>
<circle cx="60" cy="15" r="9" fill-opacity="0.3">
<animate attributename="r" from="9" to="9" begin="0s" dur="0.8s" values="9;15;9" calcmode="linear" repeatcount="indefinite"></animate>
<animate attributename="fill-opacity" from="0.5" to="0.5" begin="0s" dur="0.8s" values=".5;1;.5" calcmode="linear" repeatcount="indefinite"></animate>
</circle>
<circle cx="105" cy="15" r="15">
<animate attributename="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcmode="linear" repeatcount="indefinite"></animate>
<animate attributename="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcmode="linear" repeatcount="indefinite"></animate>
</circle>
</svg>
</div>
</div>
</a>
</div>
</div>
</section>
</div>
</article>
<div class="midwest-callout theme-primary">
<div class="callout-wrap">
<div data-controller="midwest-prose" class="midwest-prose prose">
<p class="text-sm">Engineering notes, design system updates, and occasional deep dives. No spam.</p>
</div>
<div class="mt-3 flex flex-col gap-2">
<div class="midwest-input midwest-form-group type-email" data-controller="midwest-input">
<div class="midwest-form-group__top">
<label class="midwest-label " for="email-e4f0">
</label>
</div>
<input type="email" name="email" id="email-e4f0" placeholder="you@company.com" data-midwest-input-target="input" />
</div>
<a class="midwest-button variant-default size-small fill theme-primary" name="button" href="/">
<span class="midwest-button-content">Subscribe</span>
<div class="midwest-loading-icon" role="img" aria-label="Loading" aria-hidden="true" style="display: none">
<div class="midwest-loading-icon-inner">
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg viewbox="0 0 120 30" xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="Midwest-LoadingIcon__Svg" focusable="false" aria-hidden="true">
<circle cx="15" cy="15" r="15">
<animate attributename="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcmode="linear" repeatcount="indefinite"></animate>
<animate attributename="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcmode="linear" repeatcount="indefinite"></animate>
</circle>
<circle cx="60" cy="15" r="9" fill-opacity="0.3">
<animate attributename="r" from="9" to="9" begin="0s" dur="0.8s" values="9;15;9" calcmode="linear" repeatcount="indefinite"></animate>
<animate attributename="fill-opacity" from="0.5" to="0.5" begin="0s" dur="0.8s" values=".5;1;.5" calcmode="linear" repeatcount="indefinite"></animate>
</circle>
<circle cx="105" cy="15" r="15">
<animate attributename="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcmode="linear" repeatcount="indefinite"></animate>
<animate attributename="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcmode="linear" repeatcount="indefinite"></animate>
</circle>
</svg>
</div>
</div>
</a>
</div>
</div>
</div>
</aside>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<%= midwest_layout(type: :'supporting-content', size: :lg, gap: 3) do %>
<%# ---- Main article ---- %>
<section class="min-w-0">
<%= midwest_breadcrumbs(
{ title: "Blog", path: "#" },
{ title: "Engineering", path: "#" },
{ title: "Design Systems at Scale", path: "#" }
) %>
<header class="mt-4 mb-8">
<div class="flex items-center gap-2 mb-3">
<%= midwest_badge("Design Systems") %>
<%= midwest_badge("Architecture", state: :info) %>
</div>
<h1 class="text-3xl font-bold text-theme-12 leading-tight mb-4">
Design Systems at Scale: Lessons from Building Midwest
</h1>
<div class="flex items-center gap-4">
<%= midwest_avatar(name: "Priya Nair", size: :sm, color: :violet) %>
<div class="text-sm text-theme-8">
<span class="font-medium text-theme-11">Priya Nair</span>
· May 14, 2026
· 12 min read
</div>
</div>
</header>
<%= midwest_prose do %>
<h2>Why design systems fail</h2>
<p>Most design systems start with the best intentions. A handful of components, a shared colour palette, a Figma file that everyone promises to keep updated. Then product pressure hits. Components fork. The Figma file drifts. Engineers start copy-pasting instead of composing.</p>
<p>We went through exactly this cycle before building Midwest — and the experience taught us a handful of patterns that make component libraries actually stick.</p>
<h2>Constraint is a feature</h2>
<p>Every option you expose in a component API is a decision you're pushing to every consumer. A button with twenty-three props isn't flexible — it's undecided. The most durable systems have a small, opinionated surface area and a clear path for one-off extensions.</p>
<blockquote>
<p>The best component is the one you don't have to think about.</p>
</blockquote>
<p>Midwest keeps props minimal by leaning on semantic tokens (states, sizes, variants) rather than exposing raw CSS values. This means consumers stay in the design language rather than escaping it.</p>
<h2>Co-locate styles with components</h2>
<p>Separating component styles into a global stylesheet is a maintenance trap. When the stylesheet is far from the component, both drift. Midwest co-locates a <code>.css</code> file with every component, imported via a single Tailwind entry point. Deleting a component means deleting a folder — no orphan styles left behind.</p>
<h2>Test the contract, not the implementation</h2>
<p>Unit tests for UI components should verify what the component promises to render, not how it renders it internally. We test output HTML and ARIA attributes — things that consumers depend on — and leave CSS class composition to linting. This gives us confidence to refactor internals without breaking consumers.</p>
<% end %>
<div class="mt-8 pt-6 border-t border-theme-3 flex items-center justify-between">
<%= midwest_button("← Previous article", variant: :outline) %>
<%= midwest_button("Next article →") %>
</div>
</section>
<%# ---- Sidebar ---- %>
<aside class="space-y-6">
<%# Table of contents %>
<%= midwest_card(padding: :sm) do %>
<p class="text-xs font-semibold text-theme-7 uppercase tracking-wide mb-3">On this page</p>
<nav class="space-y-1">
<% [
["Why design systems fail", true],
["Constraint is a feature", false],
["Co-locate styles", false],
["Test the contract", false],
].each do |heading, active| %>
<a href="#" class="block text-sm py-1 <%= active ? 'text-theme-9 font-medium' : 'text-theme-7 hover:text-theme-11' %>">
<%= heading %>
</a>
<% end %>
</nav>
<% end %>
<%# Author card %>
<%= midwest_card(padding: :sm) do %>
<p class="text-xs font-semibold text-theme-7 uppercase tracking-wide mb-3">Written by</p>
<div class="flex items-start gap-3">
<%= midwest_avatar(name: "Priya Nair", size: :lg, color: :violet) %>
<div>
<p class="text-sm font-semibold text-theme-11">Priya Nair</p>
<p class="text-xs text-theme-7 mt-0.5">Staff Engineer at Unabridged Software</p>
<%= midwest_button("Follow", size: :small, variant: :outline, class: "mt-2") %>
</div>
</div>
<% end %>
<%# Newsletter %>
<%= midwest_callout(state: :default, title: "Get the newsletter") do %>
<%= midwest_prose do %>
<p class="text-sm">Engineering notes, design system updates, and occasional deep dives. No spam.</p>
<% end %>
<div class="mt-3 flex flex-col gap-2">
<%= midwest_form_input(name: 'email', type: :email, placeholder: 'you@company.com') %>
<%= midwest_button("Subscribe", fill: true, size: :small) %>
</div>
<% end %>
</aside>
<% end %>

No assets to display.
Add a .js or .css file alongside the component to see it here.

Accessibility

Figma embed for Layouts/article_reader

Annotate a preview to embed its Figma design here:

@figma "https://figma.com/..."