<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged design-systems</title>
<link>https://coderlegion.com/tag/design-systems</link>
<description>Powered by Question2Answer</description>
<item>
<title>Building a Design System Alone: What I Learned and What I Would Skip</title>
<link>https://coderlegion.com/31016/building-a-design-system-alone-what-i-learned-and-what-i-would-skip</link>
<description>&lt;p&gt;Most of the writing about design systems is written by people who built them for large teams, at well-resourced companies, with dedicated time and tooling budgets. It is useful writing. It is also written for a situation that most designers and developers building real products will never be in.&lt;/p&gt;
&lt;p&gt;The reality for a solo practitioner is different. You are not staffing a design systems team. You are not writing contribution guidelines for thirty engineers. You are one person trying to build something consistent enough that it does not become a liability as the product grows, without that effort becoming a second job that competes with the actual product.&lt;/p&gt;
&lt;p&gt;I built a design system alone, in Figma, for a product I was designing and developing simultaneously. This post is about what that experience taught me: what was worth the investment, what I would approach differently, and what I wasted time on because I was solving for the wrong goal.&lt;/p&gt;
&lt;p&gt;The wrong goal, it turned out, was building a system that scales. What I actually needed was a system that is consistent today and flexible enough to grow without being rebuilt. Those sound similar. They produce very different systems.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The wrong goal is building a system that scales. The right goal is building a system that is consistent today and flexible enough to grow without being rebuilt.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;why-the-wrong-goal-is-so-easy-to-adopt&quot;&gt;WHY THE WRONG GOAL IS SO EASY TO ADOPT&lt;/h2&gt;
&lt;p&gt;Design system content is aspirational by nature. The examples are polished. The documentation is thorough. The component libraries are comprehensive. Reading enough of it creates a mental model of what a design system is supposed to look like, and that model is almost always bigger and more elaborate than what a solo practitioner actually needs.&lt;/p&gt;
&lt;p&gt;So you start building toward that model. You create a full token structure before you have designed enough screens to know which decisions are actually recurring. You document components before the components themselves are stable. You invest in a level of abstraction that makes sense when ten engineers are consuming the system and makes no sense when the only consumer is you.&lt;/p&gt;
&lt;p&gt;The overhead accumulates quietly. Maintaining the system starts to feel like work that competes with using it. And at some point you realise that the system you built is designed for a team that does not exist yet, at a scale you have not reached, for a product whose requirements are still changing.&lt;/p&gt;
&lt;p&gt;The discipline that actually serves a solo practitioner is narrower and more honest: build the minimum system that eliminates the decisions you would otherwise make repeatedly, and no more.&lt;/p&gt;
&lt;h2 id=&quot;what-i-built-in-figma-and-why&quot;&gt;WHAT I BUILT IN FIGMA AND WHY&lt;/h2&gt;
&lt;p&gt;Everything lived in Figma. No Storybook, no design tokens exported to code, no separate documentation site. Just a well-organised Figma file that served as the single source of truth for every visual decision in the product.&lt;/p&gt;
&lt;p&gt;The structure was straightforward.&lt;/p&gt;
&lt;h3 id=&quot;a-foundations-page&quot;&gt;A foundations page&lt;/h3&gt;
&lt;p&gt;One page for the decisions that everything else inherits from. Colour styles, text styles, spacing values, border radius, and shadow definitions. These were defined once and applied as Figma styles throughout every component and every screen.&lt;/p&gt;
&lt;p&gt;The discipline here was restraint. Not every colour the product uses needs a named style. The ones that carry meaning, primary, secondary, surface, border, error, success, need names. The rest can be one-off values. Naming everything creates maintenance overhead without adding clarity.&lt;/p&gt;
&lt;h3 id=&quot;a-components-page&quot;&gt;A components page&lt;/h3&gt;
&lt;p&gt;One page for reusable UI elements. Buttons, inputs, cards, badges, navigation elements, modals. Each component built as a Figma component with variants for the states that actually exist in the product: default, hover, active, disabled, error.&lt;/p&gt;
&lt;p&gt;The principle I applied consistently was to only build variants I had already designed in context. Not variants I might need. Variants that existed somewhere in the product already. This kept the component library grounded in real decisions rather than speculative ones, and it meant every component had a concrete use case rather than an abstract one.&lt;/p&gt;
&lt;h3 id=&quot;a-patterns-page&quot;&gt;A patterns page&lt;/h3&gt;
&lt;p&gt;One page for recurring compositions: form layouts, empty states, loading states, error states, page headers. These are not components in the atomic sense but they are decisions that recur and benefit from being made once rather than remade on every screen.&lt;/p&gt;
&lt;p&gt;This page turned out to be more valuable than the components page in day to day practice, because the decisions it captured were higher level and more consequential. Getting the empty state pattern right once meant every empty state in the product communicated consistently, without revisiting the decision each time.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;A patterns page turned out to be more valuable than the components page. Higher level decisions, made once, applied everywhere.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;what-i-would-approach-differently&quot;&gt;WHAT I WOULD APPROACH DIFFERENTLY&lt;/h2&gt;
&lt;p&gt;The honest answer is not that I made one big mistake I would undo. It is that I consistently solved for the wrong level of abstraction in the early stages, building infrastructure for decisions that had not stabilised yet.&lt;/p&gt;
&lt;h3 id=&quot;i-defined-tokens-before-i-had-enough-screens&quot;&gt;I defined tokens before I had enough screens&lt;/h3&gt;
&lt;p&gt;Token structures make sense when you have enough design decisions to know which values are truly recurring and which are one-offs. In the early stages of a product, you do not have that information. You have guesses.&lt;/p&gt;
&lt;p&gt;I built a colour token structure early, based on what I thought the product would need, and revised it three times as the product evolved. Each revision required updating every component that referenced the old tokens. The overhead was not catastrophic but it was real, and it was entirely self-inflicted.&lt;/p&gt;
&lt;p&gt;The better approach is to use direct Figma colour styles in the early stages and only introduce a token layer once the colour decisions have stabilised enough that you are confident they will not change shape again. Tokens are a tool for managing scale. They are overhead before you need that scale.&lt;/p&gt;
&lt;h3 id=&quot;i-documented-components-before-they-were-stable&quot;&gt;I documented components before they were stable&lt;/h3&gt;
&lt;p&gt;There is a version of design system discipline that says: document as you build. Write the usage guidelines for each component as you create it. That advice is right for a team where multiple people need to understand the decisions you made.&lt;/p&gt;
&lt;p&gt;For a solo practitioner, it is premature. The components I documented earliest were the ones I revised most, which meant the documentation was wrong almost as soon as it was written. Keeping it accurate added work without adding value, because the only person reading it was me and I already knew what had changed.&lt;/p&gt;
&lt;p&gt;I would now document only when a component is genuinely stable, meaning it has been used in multiple contexts and survived those uses without significant revision. Before that point, a brief annotation in the Figma file is enough.&lt;/p&gt;
&lt;h3 id=&quot;i-built-components-i-was-not-ready-to-use&quot;&gt;I built components I was not ready to use&lt;/h3&gt;
&lt;p&gt;Early enthusiasm for the system led me to build components speculatively, for screens I had not designed yet, for interactions I had not fully thought through. Several of those components were never used. Others were used once and then revised so substantially that the original component bore no resemblance to what ended up in the product.&lt;/p&gt;
&lt;p&gt;The constraint I now apply is simple: a component does not get built until it is needed by a screen I am actively designing. Not soon. Now. This keeps the library lean and ensures every component has a real use case driving its design rather than a hypothetical one.&lt;/p&gt;
&lt;h2 id=&quot;what-was-genuinely-worth-the-investment&quot;&gt;WHAT WAS GENUINELY WORTH THE INVESTMENT&lt;/h2&gt;
&lt;p&gt;For all the caveats above, building a design system alone was worth doing. The discipline it imposed on the product was real and the compounding returns were visible over time.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Consistency became automatic rather than effortful. Once the foundations were defined, decisions that used to require deliberation became non-decisions.&lt;/li&gt;
&lt;li&gt;Iteration got faster. Changing a primary colour across the entire product took thirty seconds because it was defined in one place. Without the system it would have taken hours and still missed something.&lt;/li&gt;
&lt;li&gt;Handoff became possible. When the product reached a point where other people needed to build on it, the system gave them a foundation to work from rather than requiring them to reverse-engineer decisions from the existing screens.&lt;/li&gt;
&lt;li&gt;The product felt more considered. Consistency at the component level produces a quality of finish that is difficult to achieve any other way, and it is immediately visible to anyone who interacts with the product professionally.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-principle-that-would-have-saved-me-time&quot;&gt;THE PRINCIPLE THAT WOULD HAVE SAVED ME TIME&lt;/h2&gt;
&lt;p&gt;If I could go back to the beginning with one piece of advice, it would be this: build the system in response to the product, not in anticipation of it.&lt;/p&gt;
&lt;p&gt;Every component, every token, every pattern should exist because the product already needed it, not because a comprehensive design system would include it. The system grows as the product grows, and the decisions it captures are real ones rather than speculative ones.&lt;/p&gt;
&lt;p&gt;This produces a smaller system than most design system content would have you build. It also produces a system you will actually maintain, because the overhead is proportional to the value it delivers, and because every part of it is grounded in a real decision that the product required you to make.&lt;/p&gt;
&lt;h2 id=&quot;closing-thought&quot;&gt;CLOSING THOUGHT&lt;/h2&gt;
&lt;p&gt;A design system built alone is not a smaller version of an enterprise design system. It is a different thing entirely: a personal discipline for making recurring decisions once and applying them consistently. The goal is not comprehensiveness. It is clarity, maintained over time, by one person, without it becoming the product instead of serving it.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31016/building-a-design-system-alone-what-i-learned-and-what-i-would-skip</guid>
<pubDate>Wed, 14 Oct 2026 05:13:20 +0000</pubDate>
</item>
<item>
<title>AI Can Generate UI. Who Keeps It Consistent? — Roman Bakurov’s Developer Journey</title>
<link>https://coderlegion.com/30253/ai-can-generate-ui-who-keeps-it-consistent-roman-bakurovs-developer-journey</link>
<description>&lt;h2 id=&quot;ai-can-generate-ui-who-keeps-it-consistent-roman-bakurov-s-developer-journey&quot;&gt;AI Can Generate UI. Who Keeps It Consistent? — Roman Bakurov’s Developer Journey&lt;/h2&gt;
&lt;blockquote&gt;&lt;p&gt;“Generating UI is easy. Keeping it consistent is the hard part.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For more than 15 years, Roman Bakurov has worked across UX, design and software development. That combination shaped the way he thinks about frontend engineering.&lt;/p&gt;
&lt;p&gt;For Roman, interfaces were never simply a visual layer. He was interested in why a product worked the way it did, how people interacted with it, and how product decisions eventually became working software.&lt;/p&gt;
&lt;p&gt;Today, he is the founder of &lt;strong&gt;Vellira&lt;/strong&gt;, an open-source cross-platform design system for React and React Native.&lt;/p&gt;
&lt;p&gt;But Vellira is about more than creating reusable UI components.&lt;/p&gt;
&lt;p&gt;Roman is exploring a bigger question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What happens to software quality when AI can generate UI faster than humans can review and maintain it?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;from-ux-and-design-to-frontend-engineering&quot;&gt;From UX and Design to Frontend Engineering&lt;/h3&gt;
&lt;p&gt;Roman's journey began at the intersection of UX, design and development.&lt;/p&gt;
&lt;p&gt;Frontend development attracted him because it provided an immediate connection between an idea and the person using it.&lt;/p&gt;
&lt;p&gt;You could take a product rule or design idea and quickly see how it behaved in a real interface.&lt;/p&gt;
&lt;p&gt;React was particularly appealing because of its compositional model. Complex interfaces could be constructed from smaller, predictable pieces with clear contracts.&lt;/p&gt;
&lt;p&gt;But as products grew, Roman noticed that creating an individual component was rarely the difficult part.&lt;/p&gt;
&lt;p&gt;The real challenge was keeping an entire product consistent.&lt;/p&gt;
&lt;p&gt;Different buttons would appear.&lt;/p&gt;
&lt;p&gt;Forms would behave differently.&lt;/p&gt;
&lt;p&gt;Developers would interpret the same design rule in different ways.&lt;/p&gt;
&lt;p&gt;And eventually, web and mobile implementations could begin evolving as if they were completely separate products.&lt;/p&gt;
&lt;p&gt;That problem became the foundation for Vellira.&lt;/p&gt;
&lt;h3 id=&quot;why-roman-built-vellira&quot;&gt;Why Roman Built Vellira&lt;/h3&gt;
&lt;p&gt;Vellira started from a practical problem.&lt;/p&gt;
&lt;p&gt;A component library can provide reusable building blocks, but a library alone cannot guarantee consistency.&lt;/p&gt;
&lt;p&gt;Someone can add local styling.&lt;/p&gt;
&lt;p&gt;Someone can create a nearly identical component.&lt;/p&gt;
&lt;p&gt;Someone can implement an existing behaviour differently.&lt;/p&gt;
&lt;p&gt;Roman wanted consistency to be supported by the architecture itself rather than depending entirely on everyone remembering the rules.&lt;/p&gt;
&lt;p&gt;Vellira began as a design system for React and React Native, with shared tokens, components, documentation, Storybook and testing.&lt;/p&gt;
&lt;p&gt;Then AI changed the equation.&lt;/p&gt;
&lt;p&gt;AI could suddenly generate working frontend code in seconds.&lt;/p&gt;
&lt;p&gt;That made writing UI less of a bottleneck.&lt;/p&gt;
&lt;p&gt;But it created another problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If AI can generate more UI, who makes sure all that UI follows the same rules?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;one-system-two-runtimes&quot;&gt;One System, Two Runtimes&lt;/h3&gt;
&lt;p&gt;One of the challenges Roman faces with Vellira is supporting both web and React Native.&lt;/p&gt;
&lt;p&gt;The platforms may need to provide similar experiences, but internally they are very different.&lt;/p&gt;
&lt;p&gt;The web has the DOM, browser semantics, focus management, keyboard navigation and ARIA.&lt;/p&gt;
&lt;p&gt;React Native has a different rendering model, event system and accessibility model.&lt;/p&gt;
&lt;p&gt;Roman therefore doesn't believe the goal should be forcing both platforms to share exactly the same implementation.&lt;/p&gt;
&lt;p&gt;Instead, the goal is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;One system of rules everywhere.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The meaning, component intent, states, sizes, tokens and core API principles should remain aligned.&lt;/p&gt;
&lt;p&gt;The implementation can still respect the platform it runs on.&lt;/p&gt;
&lt;h3 id=&quot;consistency-vs-flexibility&quot;&gt;Consistency vs Flexibility&lt;/h3&gt;
&lt;p&gt;Design systems have another difficult balance.&lt;/p&gt;
&lt;p&gt;Too much flexibility and the system becomes a collection of helpers that everyone uses differently.&lt;/p&gt;
&lt;p&gt;Too much restriction and developers start working around the system.&lt;/p&gt;
&lt;p&gt;Roman tries to separate the system contract from extension points.&lt;/p&gt;
&lt;p&gt;Core states, sizes, intents, accessibility behaviour and design-token usage should remain predictable.&lt;/p&gt;
&lt;p&gt;Composition and integration patterns can be more flexible.&lt;/p&gt;
&lt;p&gt;The principle is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A good API shouldn't make everything possible. It should make the correct path the easiest path.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;accessibility-is-not-a-final-step&quot;&gt;Accessibility Is Not a Final Step&lt;/h3&gt;
&lt;p&gt;Accessibility is another area where Roman believes architecture matters.&lt;/p&gt;
&lt;p&gt;It should not be something added after a component has already been designed.&lt;/p&gt;
&lt;p&gt;Keyboard navigation, focus behaviour, semantics, screen-reader behaviour and interactive target sizes need to be considered while the API itself is being designed.&lt;/p&gt;
&lt;p&gt;Roman also points out that accessibility is more than adding ARIA attributes.&lt;/p&gt;
&lt;p&gt;A component can have the correct &lt;code&gt;aria-label&lt;/code&gt; and still have broken keyboard navigation or poor focus management.&lt;/p&gt;
&lt;p&gt;Accessibility is fundamentally about &lt;strong&gt;behaviour and semantics&lt;/strong&gt;, not just attributes.&lt;/p&gt;
&lt;h3 id=&quot;the-hard-part-isn-t-the-button&quot;&gt;The Hard Part Isn't the Button&lt;/h3&gt;
&lt;p&gt;Ironically, the individual components are not the hardest part of Vellira.&lt;/p&gt;
&lt;p&gt;Writing a Button component is relatively straightforward.&lt;/p&gt;
&lt;p&gt;The harder problem is creating a process that makes sure every component:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;uses the correct tokens&lt;/li&gt;
&lt;li&gt;follows API conventions&lt;/li&gt;
&lt;li&gt;supports the required states&lt;/li&gt;
&lt;li&gt;preserves accessibility&lt;/li&gt;
&lt;li&gt;has tests&lt;/li&gt;
&lt;li&gt;has documentation&lt;/li&gt;
&lt;li&gt;works across the required platforms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That changes the nature of the project.&lt;/p&gt;
&lt;p&gt;Roman realised he wasn't simply building components.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;He was building a production process for components.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CI, testing, documentation, release processes, compatibility, accessibility, developer experience and package distribution all became part of the same system.&lt;/p&gt;
&lt;h3 id=&quot;when-ai-starts-generating-the-ui&quot;&gt;When AI Starts Generating the UI&lt;/h3&gt;
&lt;p&gt;AI coding tools are already extremely effective at local development tasks.&lt;/p&gt;
&lt;p&gt;They can implement components, refactor code, generate tests and help developers understand unfamiliar codebases.&lt;/p&gt;
&lt;p&gt;The problem appears when those local solutions accumulate.&lt;/p&gt;
&lt;p&gt;An AI-generated component might work perfectly while introducing a second way of doing something the system already has a standard solution for.&lt;/p&gt;
&lt;p&gt;It might use a different token.&lt;/p&gt;
&lt;p&gt;It might introduce a new pattern.&lt;/p&gt;
&lt;p&gt;It might change an API.&lt;/p&gt;
&lt;p&gt;It might implement the same principle slightly differently somewhere else.&lt;/p&gt;
&lt;p&gt;Each decision can look reasonable on its own.&lt;/p&gt;
&lt;p&gt;Together, they can slowly create a second architecture that nobody intentionally designed.&lt;/p&gt;
&lt;p&gt;The more code AI produces, the more important that problem becomes.&lt;/p&gt;
&lt;h3 id=&quot;turning-a-design-system-into-a-contract-for-ai&quot;&gt;Turning a Design System Into a Contract for AI&lt;/h3&gt;
&lt;p&gt;Roman believes the next stage of AI-assisted development needs more than better models.&lt;/p&gt;
&lt;p&gt;It needs systems that define the boundaries of acceptable solutions.&lt;/p&gt;
&lt;p&gt;Today, telling a human developer to “use our design system” may be enough.&lt;/p&gt;
&lt;p&gt;For a machine, it isn't.&lt;/p&gt;
&lt;p&gt;AI needs to understand the allowed component variants, tokens, states, composition rules, accessibility requirements and API constraints.&lt;/p&gt;
&lt;p&gt;This leads to an interesting evolution:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A design system can become a contract between the product and AI.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The system is no longer only documentation for developers.&lt;/p&gt;
&lt;p&gt;Its rules can become machine-readable and verifiable.&lt;/p&gt;
&lt;p&gt;Humans and AI may use different representations of those rules, but the underlying system should remain consistent.&lt;/p&gt;
&lt;h3 id=&quot;ai-generated-code-needs-more-than-a-successful-build&quot;&gt;AI-Generated Code Needs More Than a Successful Build&lt;/h3&gt;
&lt;p&gt;Roman doesn't believe AI-generated code should be trusted simply because it compiles or looks correct.&lt;/p&gt;
&lt;p&gt;He sees production validation as a series of independent layers.&lt;/p&gt;
&lt;p&gt;That can include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;type safety and builds&lt;/li&gt;
&lt;li&gt;unit and interaction tests&lt;/li&gt;
&lt;li&gt;accessibility checks&lt;/li&gt;
&lt;li&gt;visual regression testing&lt;/li&gt;
&lt;li&gt;design-token validation&lt;/li&gt;
&lt;li&gt;API conventions&lt;/li&gt;
&lt;li&gt;required component states&lt;/li&gt;
&lt;li&gt;platform-specific behaviour&lt;/li&gt;
&lt;li&gt;documentation&lt;/li&gt;
&lt;li&gt;unexpected changes outside the intended scope&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But even these checks are not always enough.&lt;/p&gt;
&lt;p&gt;A component can pass tests and still implement something different from what the developer originally intended.&lt;/p&gt;
&lt;p&gt;That creates another question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Did the result preserve the meaning of the original task?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For Roman, reliable AI-assisted development needs to validate not only whether the code works, but whether it matches the intended outcome.&lt;/p&gt;
&lt;h3 id=&quot;building-vellira-in-public&quot;&gt;Building Vellira in Public&lt;/h3&gt;
&lt;p&gt;Open source has also changed Roman's understanding of what makes a project successful.&lt;/p&gt;
&lt;p&gt;Code is only part of the developer experience.&lt;/p&gt;
&lt;p&gt;A project also needs to explain why it exists, make installation understandable, provide useful examples, communicate its API clearly and help developers understand the architecture.&lt;/p&gt;
&lt;p&gt;Documentation therefore becomes part of the product itself.&lt;/p&gt;
&lt;p&gt;So do naming, package structure, examples, error messages and onboarding.&lt;/p&gt;
&lt;p&gt;Building in public also creates another challenge.&lt;/p&gt;
&lt;p&gt;Something that seems obvious to the person who has worked on a project for months may be completely unclear to someone opening the repository for the first time.&lt;/p&gt;
&lt;p&gt;A good system has to make sense even to someone who wasn't present when the architectural decisions were made.&lt;/p&gt;
&lt;h3 id=&quot;from-developer-to-architect-to-founder&quot;&gt;From Developer to Architect to Founder&lt;/h3&gt;
&lt;p&gt;Building Vellira has also changed Roman's role.&lt;/p&gt;
&lt;p&gt;As a developer, he asks:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Does this implementation work?”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As an architect:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Will this still be the right decision for the whole system one or two years from now?”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And as a founder:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Are we solving a problem that actually matters to other people?”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That last question is particularly important.&lt;/p&gt;
&lt;p&gt;A technically interesting problem isn't automatically a valuable product problem.&lt;/p&gt;
&lt;p&gt;Every architectural decision can affect documentation, tests, examples, multiple platforms and existing users.&lt;/p&gt;
&lt;p&gt;The result is that Roman has become much more careful about decisions that appear convenient today but create hidden costs later.&lt;/p&gt;
&lt;h3 id=&quot;the-future-of-ui-development&quot;&gt;The Future of UI Development&lt;/h3&gt;
&lt;p&gt;Roman's work with Vellira points toward an important shift in frontend development.&lt;/p&gt;
&lt;p&gt;For years, developer productivity was largely about helping people write code faster.&lt;/p&gt;
&lt;p&gt;AI has changed that equation.&lt;/p&gt;
&lt;p&gt;Now machines can produce enormous amounts of UI code very quickly.&lt;/p&gt;
&lt;p&gt;The challenge increasingly becomes deciding &lt;strong&gt;what should be generated, what rules it must follow, and how the result can be verified.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That makes the design system more important, not less.&lt;/p&gt;
&lt;p&gt;It becomes the layer that defines consistency.&lt;/p&gt;
&lt;p&gt;It provides the constraints.&lt;/p&gt;
&lt;p&gt;It gives both humans and AI a shared understanding of how the product should behave.&lt;/p&gt;
&lt;p&gt;And perhaps most importantly, it helps prevent speed from becoming chaos.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;about-roman-bakurov&quot;&gt;About Roman Bakurov&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Roman Bakurov&lt;/strong&gt; is the founder of &lt;strong&gt;Vellira&lt;/strong&gt;, an open-source cross-platform design system for React and React Native.&lt;/p&gt;
&lt;p&gt;His work focuses on design systems, developer tooling, accessibility and reliable infrastructure for AI-assisted UI development.&lt;/p&gt;
&lt;p&gt;Vellira brings together shared design tokens, component tooling, documentation, automated quality checks and cross-platform development with the goal of making UI more consistent and maintainable.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/user/romanbakurov&quot;&gt;View Roman Bakurov's profile on CoderLegion&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://vellira.dev&quot;&gt;Visit Vellira&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;coderlegion-developer-stories&quot;&gt;CoderLegion Developer Stories&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Real developers. Real journeys. Real experiences.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Developer Stories at CoderLegion gives developers an opportunity to share how they think, what they build, the problems they encounter and what they learn along the way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interview &amp;amp; Story by Mehadi Hasan&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Dev Stories</category>
<guid isPermaLink="true">https://coderlegion.com/30253/ai-can-generate-ui-who-keeps-it-consistent-roman-bakurovs-developer-journey</guid>
<pubDate>Thu, 08 Oct 2026 09:48:37 +0000</pubDate>
</item>
<item>
<title>One Design System, Two Runtimes: Sharing Contracts Between React and React Native</title>
<link>https://coderlegion.com/28700/one-design-system-two-runtimes-sharing-contracts-between-react-and-react-native</link>
<description>&lt;p&gt;A cross-platform design system sounds simple until the first real component forces the platforms apart.&lt;/p&gt;
&lt;p&gt;React and React Native can expose the same product concept — a button, checkbox, input, modal — while relying on very different runtime primitives. The web has DOM attributes, anchors, CSS, hover and keyboard behavior. React Native has &lt;code&gt;Pressable&lt;/code&gt;, native accessibility props, gesture events, and a completely different styling model.&lt;/p&gt;
&lt;p&gt;Trying to hide all of those differences behind one implementation usually makes the API worse. In Vellira, I have been taking a different approach: &lt;strong&gt;share the semantic contract, not the runtime implementation&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That distinction has become one of the most useful architectural rules in the project.&lt;/p&gt;
&lt;h3 id=&quot;the-real-goal-is-consistency-not-identical-code&quot;&gt;The real goal is consistency, not identical code&lt;/h3&gt;
&lt;p&gt;When I started building Vellira, it was tempting to think about cross-platform components mainly as a code-sharing problem.&lt;/p&gt;
&lt;p&gt;If a web button and a native button look similar, why not make them use as much of the same component code as possible?&lt;/p&gt;
&lt;p&gt;Because the important thing users consume is not the internal component tree. It is the behavior and mental model of the public API.&lt;/p&gt;
&lt;p&gt;For a button, developers should be able to learn concepts such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;color&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;appearance&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shape&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loading&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;disabled&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fullWidth&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iconOnly&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;once and carry that knowledge between platforms.&lt;/p&gt;
&lt;p&gt;The implementation underneath those concepts does not need to be the same.&lt;/p&gt;
&lt;p&gt;This leads to a more useful definition of parity:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Cross-platform parity means shared semantics where the platforms agree, plus explicit platform-specific capabilities where they do not.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is much stronger than forcing two different runtimes through the same abstraction.&lt;/p&gt;
&lt;h3 id=&quot;put-shared-semantics-in-a-platform-neutral-contract&quot;&gt;Put shared semantics in a platform-neutral contract&lt;/h3&gt;
&lt;p&gt;Vellira keeps the common Button vocabulary in &lt;code&gt;@vellira-ui/types&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A simplified view of the shared contract looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export type ButtonSize = 'sm' | 'md' | 'lg';

export type ButtonColor =
  | 'primary'
  | 'neutral'
  | 'success'
  | 'warning'
  | 'danger';

export type ButtonAppearance =
  | 'solid'
  | 'outline'
  | 'ghost'
  | 'soft'
  | 'link';

export interface BaseButtonProps {
  color?: ButtonColor;
  appearance?: ButtonAppearance;
  size?: ButtonSize;
  shape?: 'square' | 'rounded' | 'pill';
  fullWidth?: boolean;
  loading?: boolean;
  loadingText?: string;
  disabled?: boolean;
  iconOnly?: boolean;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is nothing web-specific or native-specific in that interface. It describes what a Vellira button &lt;em&gt;means&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That shared layer gives both implementations a stable semantic center. If &lt;code&gt;appearance=&quot;soft&quot;&lt;/code&gt; exists on both platforms, it should represent the same design intent. If &lt;code&gt;loading&lt;/code&gt; disables interaction, that rule should be understood consistently too.&lt;/p&gt;
&lt;p&gt;This is where I want reuse to be strongest.&lt;/p&gt;
&lt;h3 id=&quot;let-each-platform-extend-the-contract-honestly&quot;&gt;Let each platform extend the contract honestly&lt;/h3&gt;
&lt;p&gt;Once the common semantics are defined, the platform packages can extend them instead of pretending their runtimes are identical.&lt;/p&gt;
&lt;p&gt;On the web, &lt;code&gt;ButtonProps&lt;/code&gt; extends the shared contract and normal HTML button behavior. It can also expose anchor-related properties such as &lt;code&gt;href&lt;/code&gt;, &lt;code&gt;target&lt;/code&gt;, and &lt;code&gt;rel&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export interface ButtonProps
  extends
    BaseButtonProps,
    Omit&amp;lt;ButtonHTMLAttributes&amp;lt;HTMLButtonElement&amp;gt;, 'color'&amp;gt;,
    Pick&amp;lt;
      AnchorHTMLAttributes&amp;lt;HTMLAnchorElement&amp;gt;,
      'href' | 'target' | 'rel' | 'download'
    &amp;gt; {
  children?: ReactNode;
  iconStart?: ReactNode;
  iconEnd?: ReactNode;
  tooltip?: string;
  badge?: ReactNode;
  shortcut?: ReactNode;
  asChild?: boolean;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React Native starts from the same &lt;code&gt;BaseButtonProps&lt;/code&gt;, but the runtime-specific surface is different. It builds on &lt;code&gt;PressableProps&lt;/code&gt;, exposes &lt;code&gt;onPress&lt;/code&gt;, native styles, and accessibility properties that belong to the native platform.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export interface ButtonProps
  extends BaseButtonProps, Omit&amp;lt;PressableProps, /* platform exclusions */&amp;gt; {
  children?: ReactNode;
  iconStart?: ButtonIconElement;
  iconEnd?: ButtonIconElement;
  iconSize?: number;
  onPress?: (event: GestureResponderEvent) =&amp;gt; void;
  style?: StyleProp&amp;lt;ViewStyle&amp;gt;;
  textStyle?: StyleProp&amp;lt;TextStyle&amp;gt;;
  accessibilityLabel?: string;
  testID?: string;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two APIs are clearly related, but they are not artificially identical.&lt;/p&gt;
&lt;p&gt;That is intentional.&lt;/p&gt;
&lt;p&gt;A web developer still gets normal web capabilities. A React Native developer still gets normal native capabilities. The design system owns the common semantics without becoming an abstraction tax on either platform.&lt;/p&gt;
&lt;h3 id=&quot;healthy-divergence-is-part-of-a-good-cross-platform-api&quot;&gt;Healthy divergence is part of a good cross-platform API&lt;/h3&gt;
&lt;p&gt;Some differences should not be normalized away.&lt;/p&gt;
&lt;p&gt;For example, a web button can naturally compose with an anchor or another DOM element. That is where properties such as &lt;code&gt;href&lt;/code&gt; or Vellira's &lt;code&gt;asChild&lt;/code&gt; capability make sense.&lt;/p&gt;
&lt;p&gt;React Native does not have an HTML anchor element. Inventing a fake &lt;code&gt;href&lt;/code&gt; prop just to make the type signatures look more symmetrical would create parity on paper while making the native API less natural.&lt;/p&gt;
&lt;p&gt;The same rule applies in the opposite direction. React Native has concepts such as &lt;code&gt;StyleProp&amp;lt;ViewStyle&amp;gt;&lt;/code&gt;, &lt;code&gt;testID&lt;/code&gt;, and native press events. Wrapping every one of those in invented cross-platform names would make the system harder to understand and harder to debug.&lt;/p&gt;
&lt;p&gt;So I use a simple test when deciding whether something belongs in the shared contract:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Does this property describe product-level component behavior, or does it describe the runtime that implements it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Product-level behavior is a strong candidate for the shared layer. Runtime behavior usually stays with the platform package.&lt;/p&gt;
&lt;p&gt;That distinction keeps the common API small enough to remain meaningful.&lt;/p&gt;
&lt;h3 id=&quot;share-design-decisions-separately-from-component-implementations&quot;&gt;Share design decisions separately from component implementations&lt;/h3&gt;
&lt;p&gt;Component props are only one layer of cross-platform consistency.&lt;/p&gt;
&lt;p&gt;Visual decisions such as spacing, radii, typography scales, colors, and semantic states also need a common source of truth. Those decisions can be represented as tokens and consumed differently by CSS on the web and native style objects in React Native.&lt;/p&gt;
&lt;p&gt;The important architectural point is the same: &lt;strong&gt;share the decision, adapt the execution&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A radius token does not require the web and native styling engines to work the same way. It only requires both implementations to resolve the same design intent.&lt;/p&gt;
&lt;p&gt;This is a useful pattern because it prevents &quot;cross-platform&quot; from turning into &quot;lowest common denominator&quot;. Shared semantics stay centralized while each renderer remains free to use the platform correctly.&lt;/p&gt;
&lt;h3 id=&quot;test-parity-at-the-contract-boundary&quot;&gt;Test parity at the contract boundary&lt;/h3&gt;
&lt;p&gt;Once implementations are separate, drift becomes the next problem.&lt;/p&gt;
&lt;p&gt;It is easy for the web Button to gain a new appearance or loading rule while the React Native Button silently falls behind. Separate implementations therefore need explicit validation around the things that are supposed to stay aligned.&lt;/p&gt;
&lt;p&gt;In Vellira, both Button implementations have their own tests and Storybook coverage. The shared types define a common vocabulary, while platform tests verify actual runtime behavior.&lt;/p&gt;
&lt;p&gt;That gives two different kinds of confidence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Contract confidence&lt;/strong&gt; — the shared semantic API is defined in one place.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime confidence&lt;/strong&gt; — each implementation proves that the contract behaves correctly on its own platform.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I prefer this over a large shared implementation with a growing number of platform conditionals. A shared file can look beautifully DRY while still hiding platform-specific bugs.&lt;/p&gt;
&lt;p&gt;Separate runtime tests make those differences visible.&lt;/p&gt;
&lt;h3 id=&quot;a-practical-three-layer-model&quot;&gt;A practical three-layer model&lt;/h3&gt;
&lt;p&gt;The pattern I am converging on in Vellira can be summarized as three layers.&lt;/p&gt;
&lt;h4 id=&quot;1-shared-semantics&quot;&gt;1. Shared semantics&lt;/h4&gt;
&lt;p&gt;Define the concepts developers should learn once:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;size
appearance
color
loading
disabled
shape
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These belong in platform-neutral types and design contracts.&lt;/p&gt;
&lt;h4 id=&quot;2-platform-api&quot;&gt;2. Platform API&lt;/h4&gt;
&lt;p&gt;Extend those concepts with capabilities developers expect from the runtime:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Web:          href, target, DOM attributes, asChild
React Native: onPress, accessibilityLabel, style, testID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not disguise real platform differences.&lt;/p&gt;
&lt;h4 id=&quot;3-platform-implementation&quot;&gt;3. Platform implementation&lt;/h4&gt;
&lt;p&gt;Use the native tools of each runtime:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Web:          DOM + CSS
React Native: Pressable + native styles
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The implementation can diverge significantly as long as the shared semantics remain trustworthy.&lt;/p&gt;
&lt;h3 id=&quot;what-this-changes-when-designing-new-components&quot;&gt;What this changes when designing new components&lt;/h3&gt;
&lt;p&gt;This architecture also changes how I think about adding components.&lt;/p&gt;
&lt;p&gt;Instead of beginning with &quot;How can I reuse the most code?&quot;, I begin with questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What behavior is actually common to both platforms?&lt;/li&gt;
&lt;li&gt;Which names should mean the same thing everywhere?&lt;/li&gt;
&lt;li&gt;Which states must have equivalent semantics?&lt;/li&gt;
&lt;li&gt;Which capabilities are genuinely platform-specific?&lt;/li&gt;
&lt;li&gt;What should be validated so parity cannot drift accidentally?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only after those questions are answered does implementation reuse become interesting.&lt;/p&gt;
&lt;p&gt;Sometimes there will be useful shared code below the component layer. Sometimes the correct answer is two mostly independent implementations. Both are acceptable if the public contract stays coherent.&lt;/p&gt;
&lt;p&gt;This makes the design system easier to evolve because platform differences are treated as architecture inputs, not inconvenient exceptions.&lt;/p&gt;
&lt;h3 id=&quot;the-rule-i-keep-coming-back-to&quot;&gt;The rule I keep coming back to&lt;/h3&gt;
&lt;p&gt;The most useful lesson so far is that cross-platform design systems do not need maximum code sharing. They need &lt;strong&gt;maximum semantic clarity&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Developers should feel that the React and React Native packages belong to the same system. They should recognize the vocabulary, states, design decisions, and interaction intent. But they should not have to give up the strengths of their platform to get that consistency.&lt;/p&gt;
&lt;p&gt;For Vellira, that means sharing contracts aggressively and implementations selectively.&lt;/p&gt;
&lt;p&gt;It is a smaller abstraction, but a much more durable one.&lt;/p&gt;
&lt;p&gt;You can explore the project in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.vellira.dev&quot;&gt;Vellira documentation&lt;/a&gt; or browse the implementation on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/vellira-dev/vellira&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28700/one-design-system-two-runtimes-sharing-contracts-between-react-and-react-native</guid>
<pubDate>Sat, 26 Sep 2026 09:42:11 +0000</pubDate>
</item>
</channel>
</rss>