<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Ermal's iOS Blog]]></title><description><![CDATA[Ermal's iOS Blog]]></description><link>https://ermalbujupaj.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 09 Sep 2026 06:02:30 GMT</lastBuildDate><atom:link href="https://ermalbujupaj.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a SwiftUI Design System: Tokens, Components, and Architecture]]></title><description><![CDATA[I'm building a SwiftUI design system from scratch—and documenting the journey as I go.
As a Senior iOS Engineer, I've worked on apps where every screen used slightly different shades of blue, buttons had inconsistent padding, and typography was scatt...]]></description><link>https://ermalbujupaj.hashnode.dev/building-a-swiftui-design-system-tokens-components-and-architecture</link><guid isPermaLink="true">https://ermalbujupaj.hashnode.dev/building-a-swiftui-design-system-tokens-components-and-architecture</guid><category><![CDATA[Swift]]></category><category><![CDATA[SwiftUI Libraries]]></category><category><![CDATA[SwiftUI]]></category><dc:creator><![CDATA[Ermal Bujupaj]]></dc:creator><pubDate>Sun, 01 Feb 2026 21:10:52 GMT</pubDate><content:encoded><![CDATA[<p>I'm building a SwiftUI design system from scratch—and documenting the journey as I go.</p>
<p>As a Senior iOS Engineer, I've worked on apps where every screen used slightly different shades of blue, buttons had inconsistent padding, and typography was scattered across dozens of hardcoded values. It works, until it doesn't. Refactoring becomes painful. Onboarding new developers takes longer. Design consistency slowly erodes.</p>
<p>So I decided to build a proper design system. Not just a collection of components, but a layered architecture with design tokens, reusable primitives, and clear API patterns.</p>
<p>The project is open source: <a target="_blank" href="https://github.com/ermalbujupi/swift-design-system">SwiftUIDesignSystem on GitHub</a></p>
<h2 id="heading-why-build-a-design-system">Why Build a Design System?</h2>
<p>Three reasons:</p>
<ol>
<li><p><strong>Consistency</strong> — Every button, every card, every text style comes from the same source. Change it once, it updates everywhere.</p>
</li>
<li><p><strong>Speed</strong> — Once the foundation exists, building new screens is faster. You're composing, not creating from scratch.</p>
</li>
<li><p><strong>Shared Language</strong> — Designers say "use medium spacing" and developers know exactly what that means: 16 points.</p>
</li>
</ol>
<h2 id="heading-layer-1-design-tokens">Layer 1: Design Tokens</h2>
<p>Design tokens are the foundation. They're named values—not raw numbers—that represent your design decisions.</p>
<h3 id="heading-colors">Colors</h3>
<p>Instead of scattering <code>Color(hex: "007AFF")</code> throughout the codebase, I define semantic colors:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">DSColors</span> </span>{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> primary = <span class="hljs-type">Color</span>(hex: <span class="hljs-string">"007AFF"</span>)
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> destructive = <span class="hljs-type">Color</span>(hex: <span class="hljs-string">"FF3B30"</span>)
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> success = <span class="hljs-type">Color</span>(hex: <span class="hljs-string">"34C759"</span>)

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> textPrimary = <span class="hljs-type">Color</span>(hex: <span class="hljs-string">"000000"</span>)
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> textSecondary = <span class="hljs-type">Color</span>(hex: <span class="hljs-string">"6B7280"</span>)

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> backgroundPrimary = <span class="hljs-type">Color</span>(hex: <span class="hljs-string">"FFFFFF"</span>)
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> backgroundSecondary = <span class="hljs-type">Color</span>(hex: <span class="hljs-string">"F3F4F6"</span>)
}
</code></pre>
<p>Now when I need a color, I use <code>DSColors.primary</code>—not a magic hex string.</p>
<h3 id="heading-spacing">Spacing</h3>
<p>I use a 4-point grid. Every spacing value is a multiple of 4:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">DSSpacing</span> </span>{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> xxs: <span class="hljs-type">CGFloat</span> = <span class="hljs-number">4</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> xs: <span class="hljs-type">CGFloat</span> = <span class="hljs-number">8</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> sm: <span class="hljs-type">CGFloat</span> = <span class="hljs-number">12</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> md: <span class="hljs-type">CGFloat</span> = <span class="hljs-number">16</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> lg: <span class="hljs-type">CGFloat</span> = <span class="hljs-number">24</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> xl: <span class="hljs-type">CGFloat</span> = <span class="hljs-number">32</span>
}
</code></pre>
<p>Usage is simple:</p>
<pre><code class="lang-swift"><span class="hljs-type">VStack</span>(spacing: <span class="hljs-type">DSSpacing</span>.md) {
    <span class="hljs-comment">// content</span>
}
.padding(<span class="hljs-type">DSSpacing</span>.lg)
</code></pre>
<p>No more guessing whether that padding should be 14, 16, or 18 points.</p>
<h2 id="heading-layer-2-components">Layer 2: Components</h2>
<p>With tokens in place, I build components on top of them.</p>
<h3 id="heading-dsbutton">DSButton</h3>
<p>A button with four styles (primary, secondary, destructive, ghost), three sizes, loading state, and built-in accessibility:</p>
<pre><code class="lang-swift"><span class="hljs-type">DSButton</span>(<span class="hljs-string">"Get Started"</span>, style: .primary) {
    <span class="hljs-comment">// action</span>
}

<span class="hljs-type">DSButton</span>(<span class="hljs-string">"Delete"</span>, style: .destructive, isLoading: <span class="hljs-literal">true</span>) {
    <span class="hljs-comment">// action</span>
}
</code></pre>
<h3 id="heading-dstext">DSText</h3>
<p>A typography component that enforces consistent text styles:</p>
<pre><code class="lang-swift"><span class="hljs-type">DSText</span>(<span class="hljs-string">"Welcome"</span>, style: .title1)
<span class="hljs-type">DSText</span>(<span class="hljs-string">"Subtitle here"</span>, style: .body, color: .secondary)
</code></pre>
<p>It maps to SwiftUI's built-in text styles, so Dynamic Type works automatically.</p>
<h3 id="heading-dscard">DSCard</h3>
<p>A container component using <code>@ViewBuilder</code> to wrap any content:</p>
<pre><code class="lang-swift"><span class="hljs-type">DSCard</span>(hasShadow: <span class="hljs-literal">true</span>) {
    <span class="hljs-type">VStack</span>(alignment: .leading, spacing: <span class="hljs-type">DSSpacing</span>.sm) {
        <span class="hljs-type">DSText</span>(<span class="hljs-string">"Card Title"</span>, style: .headline)
        <span class="hljs-type">DSText</span>(<span class="hljs-string">"Card content goes here."</span>, style: .body, color: .secondary)
        <span class="hljs-type">DSButton</span>(<span class="hljs-string">"Action"</span>, style: .primary) { }
    }
}
</code></pre>
<h2 id="heading-what-ive-learned-so-far">What I've Learned So Far</h2>
<p><strong>Start with tokens, not components.</strong> It's tempting to jump straight into building buttons and cards. But without tokens, you'll hardcode values and lose consistency.</p>
<p><strong>Keep the API simple.</strong> A component that requires 10 parameters to use won't get used. Sensible defaults matter.</p>
<p><strong>Previews are essential.</strong> Xcode previews let me see every component variant without running the app. I add multiple <code>#Preview</code> blocks to each component.</p>
<h2 id="heading-whats-next">What's Next</h2>
<p>I'm continuing to build out the component library:</p>
<ul>
<li><p>DSTextField (input with validation)</p>
</li>
<li><p>DSToggle (accessible switch)</p>
</li>
<li><p>DSAlert (contextual messages)</p>
</li>
<li><p>DSBadge (status indicators)</p>
</li>
</ul>
<p>The goal is a production-ready system that I—or anyone—can drop into a real app.</p>
<p>Follow the progress on <a target="_blank" href="https://github.com/ermalbujupi/swift-design-system">GitHub</a>, or connect with me on <a target="_blank" href="https://twitter.com/ermalbujupaj">Twitter/X</a>.</p>
]]></content:encoded></item></channel></rss>