{"id":9733,"date":"2025-08-31T16:15:00","date_gmt":"2025-08-31T13:15:00","guid":{"rendered":"https:\/\/handoli.com\/index.php\/2025\/08\/31\/building-a-design-system-at-genius-scan\/"},"modified":"2025-08-31T16:15:00","modified_gmt":"2025-08-31T13:15:00","slug":"building-a-design-system-at-genius-scan","status":"publish","type":"post","link":"https:\/\/handoli.com\/index.php\/2025\/08\/31\/building-a-design-system-at-genius-scan\/","title":{"rendered":"Building a design system at Genius Scan"},"content":{"rendered":"<p>As an app\u2019s code base grows and evolves, it can be really tricky to maintain consistency within its UI. It\u2019s easy for things like margins and padding, or colors and fonts, to start diverging, as different developers might use different values within each individual UI component\u2019s implementation. At the same time, UI code can also become increasingly complex, since new features might be designed and implemented completely from scratch, which often leads to a minimum amount of code reuse and duplicated engineering efforts.<\/p>\n<p>That\u2019s where some form of <em>design system<\/em> can be incredibly useful. Although a design system can take many different shapes and forms, the general idea is to formalize an app\u2019s design and overall UI, by creating various components that can easily be reused within different contexts \u2014 leading to a more consistent, decoupled, and reusable overall implementation (both in terms of design and code).<\/p>\n<p>At first, it might seem really tricky to start building such a system \u2014 as it\u2019s common to consider it to be an enormous project that will require months to complete. Not to mention that the whole app then seemingly needs be rewritten to adopt the new system. The good news, though, is that a design system can very often be built incrementally, and there\u2019s really no need to rewrite anything to get started.<\/p>\n<p>So, in this article, I\u2019d like to share how I\u2019ve been helping the team at <a href=\"https:\/\/thegrizzlylabs.com\/genius-scan\">Genius Scan<\/a> (which is also the company that has helped me bring back Swift by Sundell after a long hiatus, by <a href=\"https:\/\/geniusscansdk.com\/swiftbysundell\">promoting their SDK<\/a>) to build an initial design system, which is being incrementally adopted across all of the company\u2019s various code bases.<\/p>\n<p>This article won\u2019t be a step-by-step tutorial on how to build a design system for any app, but rather an example (real-world, although somewhat edited to work well for an article) of how to approach the task of building such a system. I hope it\u2019ll be interesting, and serve as a nice source of inspiration.<\/p>\n<h2>Picking an entry point<\/h2>\n<p>Like all good engineering solutions, building a design system should aim to solve an actual problem that we\u2019re facing within a given project. So, in the case of Genius Scan, what prompted us to start building such a system was that we had been struggling to maintain consistency (which in turn lead to duplicate bug fixes, and code duplication) within certain parts of our UI code \u2014 specifically within our various <em>lists<\/em>.<\/p>\n<p>Like many other iOS apps, Genius Scan has a lot of list views \u2014 which let you view your scanned documents, folders, export services, and so on. So the first goal for our design system was to create a robust solution for building such lists, that would enable us to share as much code and design properties as possible between them, while still having a system that would be flexible enough to accommodate each list\u2019s different needs.<\/p>\n<p>By picking an initial, reasonably sized goal like that, and not requiring an entire huge set of components to be built up front, we would be able to build and test the initial version of our design system quite quickly, and incrementally adopt it whenever we built a new list, or when an existing one was significantly updated or refactored.<\/p>\n<h2>Composition is key<\/h2>\n<p>One mistake that\u2019s sometimes made when building design systems is to lock them down too much around a specific set of prepared components. After all, even in the most consistent app code bases, different features will require different component tweaks, and app designs also tend to change over time.<\/p>\n<p>So, just like how Apple designed SwiftUI\u2019s overall API, placing a strong focus on <em>composition<\/em> can be incredibly important in order to create a design system that\u2019ll actually stand the test of time. Essentially, we want to create a set of robust, well-defined building blocks, that can then be combined in order to create the actual UI for the features that we want to build.<\/p>\n<p>In the case of Genius Scan, since our initial focus was on lists, we started by creating a basic <code>Row<\/code> component, which would simply combine a leading and trailing element into a horizontal row:<\/p>\n<pre class=\"splash\"><code><span class=\"s-comment\">\/\/ We define all of our design system APIs as 'public', since\n\/\/ we're implementing our system as a separate Swift package.<\/span>\n<span class=\"s-keyword\">public struct<\/span> Row&lt;Leading: <span class=\"s-type\">View<\/span>, Trailing: <span class=\"s-type\">View<\/span>&gt;: <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">public var<\/span> leading: <span class=\"s-type\">Leading<\/span>\n    <span class=\"s-keyword\">public var<\/span> trailing: <span class=\"s-type\">Trailing<\/span>\n\n    <span class=\"s-keyword\">public init<\/span>(\n        <span class=\"s-keyword\">@ViewBuilder<\/span> leading: () -&gt; <span class=\"s-type\">Leading<\/span>,\n        <span class=\"s-keyword\">@ViewBuilder<\/span> trailing: () -&gt; <span class=\"s-type\">Trailing<\/span> = <span class=\"s-type\">EmptyView<\/span>.<span class=\"s-keyword\">init<\/span>\n    ) {\n        <span class=\"s-keyword\">self<\/span>.<span class=\"s-property\">leading<\/span> = <span class=\"s-call\">leading<\/span>()\n        <span class=\"s-keyword\">self<\/span>.<span class=\"s-property\">trailing<\/span> = <span class=\"s-call\">trailing<\/span>()\n    }\n    \n    <span class=\"s-keyword\">public var<\/span> body: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">HStack<\/span> {\n            leading\n\n            trailing\n                .<span class=\"s-call\">padding<\/span>(.<span class=\"s-dotAccess\">leading<\/span>)\n                .<span class=\"s-call\">frame<\/span>(maxWidth: .<span class=\"s-dotAccess\">infinity<\/span>, alignment: .<span class=\"s-dotAccess\">trailing<\/span>)\n        }\n        .<span class=\"s-call\">frame<\/span>(maxWidth: .<span class=\"s-dotAccess\">infinity<\/span>, alignment: .<span class=\"s-dotAccess\">leading<\/span>)\n    }\n}<\/code><\/pre>\n<p class=\"info\">Note how we evaluate both of our content closures within the view\u2019s initializer, rather than within its <code>body<\/code>. That\u2019s to avoid re-evaluating the closures every time the view is updated. To learn more, check out <a href=\"https:\/\/www.swiftbysundell.com\/articles\/swiftui-viewbuilder-tips-and-tricks\/\">\u201cTips and tricks for when using SwiftUI\u2019s ViewBuilder\u201d<\/a>.<\/p>\n<p>The above component might not look that useful by itself, but it serves as a great starting point for building increasingly more complex row-based components. For example, using the above <code>Row<\/code>, we can now construct a specialized version of it for rendering a row that has a label and a text field \u2014 like this:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">public struct<\/span> TextFieldRow: <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">public var<\/span> title: <span class=\"s-type\">String<\/span>\n    <span class=\"s-keyword\">public var<\/span> placeholder: <span class=\"s-type\">String<\/span>\n    <span class=\"s-keyword\">@Binding public var<\/span> text: <span class=\"s-type\">String<\/span>\n\n    <span class=\"s-keyword\">@FocusState private var<\/span> isTextFieldFocused\n\n    <span class=\"s-keyword\">public init<\/span>(title: <span class=\"s-type\">String<\/span>, placeholder: <span class=\"s-type\">String<\/span>, text: <span class=\"s-type\">Binding<\/span>&lt;<span class=\"s-type\">String<\/span>&gt;) {\n        <span class=\"s-keyword\">self<\/span>.<span class=\"s-property\">title<\/span> = title\n        <span class=\"s-keyword\">self<\/span>.<span class=\"s-property\">placeholder<\/span> = placeholder\n        _text = text\n    }\n\n    <span class=\"s-keyword\">public var<\/span> body: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">Row<\/span>(leading: {\n            <span class=\"s-type\">Text<\/span>(title)\n                .<span class=\"s-call\">bold<\/span>()\n                .<span class=\"s-call\">foregroundStyle<\/span>(isTextFieldFocused ? .<span class=\"s-dotAccess\">orange<\/span> : .<span class=\"s-dotAccess\">primary<\/span>)\n                .<span class=\"s-call\">onTapGesture<\/span> { isTextFieldFocused = <span class=\"s-keyword\">true<\/span> }\n        }, trailing: {\n            <span class=\"s-type\">TextField<\/span>(placeholder, text: <span class=\"s-property\">$text<\/span>)\n                .<span class=\"s-call\">multilineTextAlignment<\/span>(.<span class=\"s-dotAccess\">trailing<\/span>)\n                .<span class=\"s-call\">focused<\/span>(<span class=\"s-property\">$isTextFieldFocused<\/span>)\n        })\n    }\n}<\/code><\/pre>\n<p>We\u2019re still building the foundation for our new design system, but we now already have a component that\u2019s ready to be used as-is in a wide number of scenarios \u2014 for example whenever the user is entering text into some kind of form. But where the true power of a design system starts to show is when we start building in functionality into our components that all call sites get for free.<\/p>\n<p>For example, if we add a <code>contentType<\/code> parameter to our <code>TextFieldRow<\/code>, then we can use that both to give the system a hint regarding what kind of text that the user is entering, as well as to choose the appropriate keyboard for that content type, and even automatically use a <code>SecureField<\/code> when the user is expected to input a password:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">public struct<\/span> TextFieldRow: <span class=\"s-type\">View<\/span> {\n    ...\n    <span class=\"s-keyword\">public var<\/span> contentType: <span class=\"s-type\">UITextContentType<\/span>?\n    ...\n\n    <span class=\"s-keyword\">public init<\/span>(\n        title: <span class=\"s-type\">String<\/span>,\n        placeholder: <span class=\"s-type\">String<\/span>,\n        contentType: <span class=\"s-type\">UITextContentType<\/span>?,\n        text: <span class=\"s-type\">Binding<\/span>&lt;<span class=\"s-type\">String<\/span>&gt;\n    ) {\n        ...\n        <span class=\"s-keyword\">self<\/span>.<span class=\"s-property\">contentType<\/span> = contentType\n        ...\n    }\n\n    <span class=\"s-keyword\">public var<\/span> body: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">Row<\/span>(leading: {\n            ...\n        }, trailing: {\n            inputView\n                .<span class=\"s-call\">multilineTextAlignment<\/span>(.<span class=\"s-dotAccess\">trailing<\/span>)\n                .<span class=\"s-call\">textContentType<\/span>(contentType)\n                .<span class=\"s-call\">keyboardType<\/span>(contentType?.<span class=\"s-property\">matchingKeyboardType<\/span> ?? .<span class=\"s-dotAccess\">default<\/span>)\n                .<span class=\"s-call\">focused<\/span>(<span class=\"s-property\">$isTextFieldFocused<\/span>)\n        })\n    }\n\n    <span class=\"s-keyword\">@ViewBuilder private var<\/span> inputView: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-keyword\">switch<\/span> contentType {\n        <span class=\"s-keyword\">case<\/span> .<span class=\"s-dotAccess\">password<\/span>, .<span class=\"s-dotAccess\">newPassword<\/span>:\n            <span class=\"s-type\">SecureField<\/span>(placeholder, text: <span class=\"s-property\">$text<\/span>)\n        <span class=\"s-keyword\">default<\/span>:\n            <span class=\"s-type\">TextField<\/span>(placeholder, text: <span class=\"s-property\">$text<\/span>)\n        }\n    }\n}<\/code><\/pre>\n<p>One core aspect of SwiftUI that really makes it a great fit for building a design system is how easy it typically makes extracting one part of a component into a brand new implementation \u2014 which in turn gives us further opportunities for composition and code reuse.<\/p>\n<p>For example, let\u2019s say that we now wanted to make the above <code>UITextContentType<\/code>-related logic reusable, so that it\u2019s not specifically tied to <code>TextFieldRow<\/code>. That could be done by extracting all of those parts into a new <code>TextInputView<\/code>, which we\u2019d then make <code>TextFieldRow<\/code> use:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">public struct<\/span> TextInputView: <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">public var<\/span> placeholder: <span class=\"s-type\">String<\/span>\n    <span class=\"s-keyword\">public var<\/span> contentType: <span class=\"s-type\">UITextContentType<\/span>?\n    <span class=\"s-keyword\">@Binding public var<\/span> text: <span class=\"s-type\">String<\/span>\n\n    <span class=\"s-keyword\">public init<\/span>(\n        placeholder: <span class=\"s-type\">String<\/span>,\n        contentType: <span class=\"s-type\">UITextContentType<\/span>?,\n        text: <span class=\"s-type\">Binding<\/span>&lt;<span class=\"s-type\">String<\/span>&gt;\n    ) {\n        <span class=\"s-keyword\">self<\/span>.<span class=\"s-property\">placeholder<\/span> = placeholder\n        <span class=\"s-keyword\">self<\/span>.<span class=\"s-property\">contentType<\/span> = contentType\n        _text = text\n    }\n\n    <span class=\"s-keyword\">public var<\/span> body: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        inputView\n            .<span class=\"s-call\">textContentType<\/span>(contentType)\n            .<span class=\"s-call\">keyboardType<\/span>(contentType?.<span class=\"s-property\">matchingKeyboardType<\/span> ?? .<span class=\"s-dotAccess\">default<\/span>)\n    }\n\n    <span class=\"s-keyword\">@ViewBuilder private var<\/span> inputView: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-keyword\">switch<\/span> contentType {\n        <span class=\"s-keyword\">case<\/span> .<span class=\"s-dotAccess\">password<\/span>, .<span class=\"s-dotAccess\">newPassword<\/span>:\n            <span class=\"s-type\">SecureField<\/span>(placeholder, text: <span class=\"s-property\">$text<\/span>)\n        <span class=\"s-keyword\">default<\/span>:\n            <span class=\"s-type\">TextField<\/span>(placeholder, text: <span class=\"s-property\">$text<\/span>)\n        }\n    }\n}\n\n<span class=\"s-keyword\">public struct<\/span> TextFieldRow: <span class=\"s-type\">View<\/span> {\n    ...\n\n    <span class=\"s-keyword\">public var<\/span> body: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">Row<\/span>(leading: {\n           ...\n        }, trailing: {\n            <span class=\"s-type\">TextInputView<\/span>(\n                placeholder: placeholder,\n                contentType: contentType,\n                text: <span class=\"s-property\">$text<\/span>\n            )\n            .<span class=\"s-call\">multilineTextAlignment<\/span>(.<span class=\"s-dotAccess\">trailing<\/span>)\n            .<span class=\"s-call\">focused<\/span>(<span class=\"s-property\">$isTextFieldFocused<\/span>)\n        })\n    }\n}<\/code><\/pre>\n<p>So, as the above examples illustrate, the process of getting started building a design system can really be quite simple, and as long as we focus on implementing composable building blocks, we\u2019re quite likely to end up with a flexible solution that doesn\u2019t require a lot of complex logic within each specific component.<\/p>\n<p>For example, if a given feature within our app needs to show a label that\u2019s different from the one that the default <code>TextFieldRow<\/code> uses, then that feature can now simply compose the root <code>Row<\/code> type with <code>TextInputView<\/code>, and define its own <code>leading<\/code> label for such rows, without having to add that additional complexity within our design system itself. That way the system itself can stay well-organized and focused, and defer all specialization to each individual feature.<\/p>\n<h2>The power of the environment<\/h2>\n<p>When it comes to specialization and customization, we do still want to enable our components to be tweaked to some extent, so that we won\u2019t end up requiring a brand new (composed) implementation each time. Otherwise, we can often end up with another problem \u2014 that very few features actually end up <em>using<\/em> the design system that we\u2019ve built, since building components from scratch ends up being easier.<\/p>\n<p>The key here is to strike a nice balance between adding too much feature-specific functionality into our design system itself, versus making our components too simple and therefore not practically useable (which is also a big reason why it\u2019s typically a good idea to have feature work drive the implementation of our design system, rather than building it in complete isolation).<\/p>\n<p>For example, in the case of Genius Scan, some of the lists that we were building required each row to be inset with a certain amount of padding. Now, we could of course add support for that by adding a <code>padding<\/code> parameter to each of our row components, but that wouldn\u2019t really be in favor of our consistency goal, and would also likely lead to code duplication and lots of magic numbers.<\/p>\n<p>Instead, let\u2019s take a look at how we can add a mechanism for changing a given row\u2019s style using the <em>SwiftUI environment<\/em>.<\/p>\n<p>To get started, let\u2019s define a root <code>Configuration<\/code> value that we\u2019re going to use to encapsulate all of our design system\u2019s configurable parameters \u2014 which will also give us a great overview of what kind of design system aspects that are currently user-adjustable. In the case of Genius Scan, that configuration type ended up looking something like this:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">struct<\/span> Configuration {\n    <span class=\"s-keyword\">var<\/span> rows = <span class=\"s-type\">Row<\/span>.<span class=\"s-type\">Configuration<\/span>()\n    <span class=\"s-keyword\">var<\/span> colors = <span class=\"s-type\">Color<\/span>.<span class=\"s-type\">Configuration<\/span>()\n    <span class=\"s-keyword\">var<\/span> icons = <span class=\"s-type\">Icon<\/span>.<span class=\"s-type\">Configuration<\/span>()\n    ...\n}\n\n<span class=\"s-keyword\">extension<\/span> <span class=\"s-type\">EnvironmentValues<\/span> {\n    <span class=\"s-keyword\">@Entry var<\/span> configuration = <span class=\"s-type\">Configuration<\/span>()\n}<\/code><\/pre>\n<blockquote>\n<p>Note how we also define an <code>@Entry<\/code> for our configuration within SwiftUI\u2019s <code>EnvironmentValues<\/code> type. That\u2019s so that we later will be able to get and set our design system\u2019s configuration values through the SwiftUI environment.<\/p>\n<\/blockquote>\n<p>As we can see above, the root <code>Configuration<\/code> type essentially just encapsulates other configuration types that are specific to each component category, such as <code>rows<\/code>. That way, we can prevent <code>Configuration<\/code> from having too many properties, while also adding a bit of extra structure to our configuration values.<\/p>\n<p>Looking closer at <code>Row.Configuration<\/code> in particular, it\u2019ll enable us to change which <code>RowStyle<\/code> to use, and what background color to use for rows that are using the <code>inset<\/code> style:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">public enum<\/span> RowStyle {\n    <span class=\"s-keyword\">case<\/span> plain\n    <span class=\"s-keyword\">case<\/span> inset\n}\n\n<span class=\"s-keyword\">extension<\/span> <span class=\"s-type\">Row<\/span> <span class=\"s-keyword\">where<\/span> <span class=\"s-type\">Leading<\/span> == <span class=\"s-type\">Never<\/span>, <span class=\"s-type\">Trailing<\/span> == <span class=\"s-type\">Never<\/span> {\n    <span class=\"s-keyword\">struct<\/span> <span class=\"s-type\">Configuration<\/span> {\n        <span class=\"s-keyword\">var<\/span> style = <span class=\"s-type\">RowStyle<\/span>.<span class=\"s-property\">plain<\/span>\n        <span class=\"s-keyword\">var<\/span> insetBackgroundColor = <span class=\"s-type\">Color<\/span>.<span class=\"s-property\">secondary<\/span>\n        ...\n    }\n}<\/code><\/pre>\n<blockquote>\n<p>Above we\u2019re specifying <code>Never<\/code> as the generic constraint for both our <code>Leading<\/code> and <code>Trailing<\/code> types, since we want to use a single, non-generic <code>Configuration<\/code> type for all of our <code>Row<\/code> variants. In theory, we could\u2019ve picked any other type to constrain to (such as <code>Text<\/code>, or <code>Color<\/code>), but <code>Never<\/code> is a quite good fit here since otherwise it might seem like our configuration only applies to rows with a specific kind of leading or training content.<\/p>\n<\/blockquote>\n<p>With the above pieces in place, let\u2019s now update our <code>Row<\/code> component from before, by retrieving the <code>row<\/code> part of our design system\u2019s configuration from the current environment, and to then use those configuration values to determine how to render each row\u2019s <code>content<\/code> (which is the exact same UI code that we originally implemented directly within the <code>body<\/code> property):<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">public struct<\/span> Row&lt;Leading: <span class=\"s-type\">View<\/span>, Trailing: <span class=\"s-type\">View<\/span>&gt;: <span class=\"s-type\">View<\/span> {\n    ...\n    <span class=\"s-keyword\">@Environment<\/span>(.<span class=\"s-property\">configuration<\/span>.<span class=\"s-property\">rows<\/span>) <span class=\"s-keyword\">private var<\/span> configuration\n    ...\n\n    <span class=\"s-keyword\">public var<\/span> body: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-keyword\">switch<\/span> configuration.<span class=\"s-property\">style<\/span> {\n        <span class=\"s-keyword\">case<\/span> .<span class=\"s-dotAccess\">plain<\/span>:\n            content\n        <span class=\"s-keyword\">case<\/span> .<span class=\"s-dotAccess\">inset<\/span>:\n            content\n                .<span class=\"s-call\">padding<\/span>()\n                .<span class=\"s-call\">background<\/span>(configuration.<span class=\"s-property\">insetBackgroundColor<\/span>)\n        }\n    }\n    \n    <span class=\"s-keyword\">private var<\/span> content: <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-type\">HStack<\/span> {\n            leading\n\n            trailing\n                .<span class=\"s-call\">padding<\/span>(.<span class=\"s-dotAccess\">leading<\/span>)\n                .<span class=\"s-call\">frame<\/span>(maxWidth: .<span class=\"s-dotAccess\">infinity<\/span>, alignment: .<span class=\"s-dotAccess\">trailing<\/span>)\n        }\n        .<span class=\"s-call\">frame<\/span>(maxWidth: .<span class=\"s-dotAccess\">infinity<\/span>, alignment: .<span class=\"s-dotAccess\">leading<\/span>)\n    }\n}<\/code><\/pre>\n<p>Alright, just one piece of the puzzle left. You might have noticed that we didn\u2019t actually make our <code>Configuration<\/code> type <code>public<\/code> earlier, even though we of course want users of our design system to be able to change configuration values from within a consuming app target.<\/p>\n<p>The reason for that is because we instead want to define explicit public APIs for changing different aspects of each configuration \u2014 by implementing modifier-like <code>View<\/code> methods that will blend in very nicely with SwiftUI\u2019s own APIs. That way, our <code>Configuration<\/code> type becomes an internal implementation detail, and we can tailor what kind of configuration aspects that we want to give each call site control over, like this:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">public extension<\/span> <span class=\"s-type\">View<\/span> {\n    <span class=\"s-keyword\">func<\/span> rowStyle(<span class=\"s-keyword\">_<\/span> style: <span class=\"s-type\">RowStyle<\/span>) -&gt; <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-call\">environment<\/span>(.<span class=\"s-property\">configuration<\/span>.<span class=\"s-property\">rows<\/span>.<span class=\"s-property\">style<\/span>, style)\n    }\n    \n    <span class=\"s-keyword\">func<\/span> rowInsetBackgroundColor(<span class=\"s-keyword\">_<\/span> color: <span class=\"s-type\">Color<\/span>) -&gt; <span class=\"s-keyword\">some<\/span> <span class=\"s-type\">View<\/span> {\n        <span class=\"s-call\">environment<\/span>(.<span class=\"s-property\">configuration<\/span>.<span class=\"s-property\">rows<\/span>.<span class=\"s-property\">insetBackgroundColor<\/span>, color)\n    }\n}<\/code><\/pre>\n<blockquote>\n<p>Above we\u2019re allowing any <code>Color<\/code> value to be set as the row inset background color, which favors flexibility. However, if we\u2019d like to restrict our API users to instead only pick from a set of pre-defined colors, then we could\u2019ve defined our own <code>Color<\/code> enum instead of using SwiftUI\u2019s color type directly.<\/p>\n<\/blockquote>\n<p>By continuously using the principles that we explored above, we can take this very early beginning of a design system and keep scaling it up \u2014 to add more components, more APIs, and to increase adoption within any apps that consume our system. A design system will probably never be \u201cfinished\u201d, as it will likely need to be an ever-evolving code base that\u2019s adapted to new designs, as well as system changes from Apple \u2014 such as the introduction of Liquid Glass in iOS 26.<\/p>\n<h2>Conclusion<\/h2>\n<p>A design system is not something that you\u2019d typically start building when an app\u2019s code base is relatively small, but once a code base reaches a point where UI consistency and code duplication starts to become an issue, then building a design system can be a great solution to such problems.<\/p>\n<p>Plus, I\u2019ve personally found that building a design system can often really help make the collaboration between developers and designers much better. When everyone involved in designing and building an app\u2019s UI is focused on how different components can be defined and then later composed, then the process of translating designs into code often becomes so much more streamlined, and communication becomes smother too \u2014 since everyone now has a shared \u201cvocabulary\u201d of sorts when talking about the app\u2019s UI.<\/p>\n<p>I hope that you found this article interesting. If you have any questions, comments, or feedback, then feel free to each out via either <a href=\"https:\/\/mastodon.social\/@johnsundell\">Mastodon<\/a> or <a href=\"https:\/\/bsky.app\/profile\/johnsundell.bsky.social\">Bluesky<\/a>.<\/p>\n<p>Thanks for reading!<\/p>","protected":false},"excerpt":{"rendered":"<p>As an app\u2019s code base grows and evolves, it can be really tricky to maintain consistency within its UI. It\u2019s easy for things like margins and padding, or colors and fonts, to start diverging, as different developers might use different values within each individual UI component\u2019s implementation. At the same time, UI code can also [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rop_custom_images_group":[],"rop_custom_messages_group":[],"rop_publish_now":"initial","rop_publish_now_accounts":[],"rop_publish_now_history":[],"rop_publish_now_status":"pending","footnotes":""},"categories":[1,15],"tags":[],"class_list":["post-9733","post","type-post","status-publish","format-standard","hentry","category-explore","category-world"],"_links":{"self":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/posts\/9733","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/comments?post=9733"}],"version-history":[{"count":0,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/posts\/9733\/revisions"}],"wp:attachment":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/media?parent=9733"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/categories?post=9733"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/tags?post=9733"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}