All Underneath A Design System

When working in frontend development, it is common to come across concepts such as Design System, Storybook, library, package, versioning, bundle, registries, artifacts. These are all quite different things, but they are so closely related that they can be difficult to understand when studied separately. This article presents the complete system: from the creation of a component to the moment that component ends up running inside an application's browser.
1. What is a Design System, really?
A Design System is a broader concept than a component library. It is a set of rules, decisions, patterns, and tools that allow an organization to build interfaces consistently.
It can include:
- design colors and tokens
- typography
- spacing
- sizes
- borders and radii
- shadows
- iconography
- accessibility principles
- interaction patterns
- reusable components
- documentation
- guidelines on when and how to use each component
- designs in tools such as Figma
- implementations of those components in code
Therefore:
A Design System is not necessarily a package.
A company could have a Design System defined in visual and UX terms without yet having a reusable component library.
However, in a software company it is very common for the Design System to have a technical implementation, for example a React component library:
DESIGN SYSTEM
ā
āāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāā
ā ā ā
Design Guidelines Code
ā ā ā
Figma Documentation React libraryThe library is therefore one of the technical pieces that materialize the Design System.
2. Why does a Design System library exist?
Imagine a company with several frontend applications:
Company
ā
āāā Booking App
āāā Dashboard
āāā Website Builder
āāā Owner Portal
āāā AdminThey all need buttons, modals, inputs, selects, tables, date pickers, etc.
Without a shared library, each team could implement its own components. Over time, differences might appear:
Dashboard Button
ā padding: 12px
ā blue: #0066FF
Booking Button
ā padding: 10px
ā blue: #0055DD
Admin Button
ā padding: 12px
ā blue: #0066FF
ā radius: 6pxEven though they are all called "Button", their behavior and appearance could diverge.
A shared library allows those implementations to be centralized:
@company/design-system
ā
āāāāāāāāāāāāāā¼āāāāāāāāāāāāā
ā ā ā
Booking Dashboard Admin
ā ā ā
āāāāāāāāā imports āāāāāāāāāAn application can then write:
import { Button, Modal, Input } from '@company/design-system'and directly use the components that another part of the organization has already implemented.
This provides reuse, consistency, and a common API across teams.
3. It is a library, not an application
A traditional application ultimately aims to provide a service to a user:
Frontend application
ā
UserA library has a different type of consumer:
Library
ā
Developer
ā
Application
ā
UserWhen developing a library, the developers who use it are its immediate consumers.
That is why the library API is especially important.
For example:
<Button
variant="primary"
size="large"
loading
>
Book now
</Button>For the team maintaining the library, that API is a fundamental part of the product. Consumers do not need to know how the button works internally; they need to know how to use it.
This is similar to a backend API: the consumer interacts with a stable interface without needing to know the entire internal implementation. Today, it is becoming increasingly important to make libraries "agent-friendly"; in other words, since AI agents will be one of their main consumers, we need to make them more accessible to those agents.
4. Storybook is not the Design System or the library
It is important to separate these concepts.
Design System ā a set of rules, patterns, components, and design decisions.
Component library ā a reusable implementation of those components in code.
Storybook ā a tool for developing, visualizing, documenting, and testing UI components.
For example, a library might have:
design-system/
ā
āāā src/
ā āāā Button/
ā ā āāā Button.tsx
ā ā āāā Button.stories.tsx
ā
ā āāā Modal/
ā ā āāā Modal.tsx
ā ā āāā Modal.stories.tsx
ā
ā āāā Input/
ā āāā Input.tsx
ā āāā Input.stories.tsx
ā
āāā package.json
āāā .storybook/Button.tsx contains the actual component:
export function Button() {
return <button>...</button>
}While Button.stories.tsx tells Storybook how to display it:
export default {
title: 'Components/Button',
component: Button,
}
export const Primary = {
args: {
children: 'Book now',
},
}Storybook can then present different states:
Button
āāā Primary
āāā Secondary
āāā Disabled
āāā Loading
āāā Long textTherefore, it can be useful to think of Storybook as a work environment for a UI library. It allows developers to view and manipulate components without having to build a complete application around them.
Storybook can also be used alongside testing, accessibility, and visual regression tools.
5. A library does not need a server to work
This distinction is fairly basic, but many developers do not have the complete picture of this part of the delivery process, and it is worth stopping here because we will later discuss more complicated concepts around orchestrating versions, and we need to establish the fundamentals first.
A frontend application normally ends up being something that is served to a browser:
Browser
ā
Frontend application
ā
Backend/APIA library is different. A library can simply be a collection of files that another project downloads and uses:
Application
ā
Package
ā
JavaScript / CSS / typesThere is not necessarily a "Design System server" running constantly. This is one of the things that distinguishes a library from a service.
For example, an application might do this:
Browser
ā
ā HTTP request
ā¼
Backend serviceWhereas an application that uses a library does something conceptually similar to:
Source code
ā
ā import
ā¼
Library packageThe library is consumed during the development and build process of the application.
6. Where does the library live?
This is where the concept of a package registry appears.
When you run:
npm install @company/design-systemnpm looks for that package in a registry.
The best-known public registry is the npm registry, but companies can also use private registries such as GitHub Packages, GitLab Package Registry, AWS CodeArtifact, Artifactory, or other internal systems.
Conceptually:
Package Registry
ā
āāāāāāāāāāāāāā¼āāāāāāāāāāāāā
ā ā ā
@company/ui @company/icons @company/utilsA company can publish its own packages there:
@company/design-system@1.2.3
@company/icons@3.1.0
@company/api-client@2.4.1The registry is, simplifying things, a system specialized in storing and distributing versions of packages.
7. What happens when the package is installed?
Suppose a Todo List application needs the Design System:
npm install @company/design-systemnpm downloads the package and its dependencies and places them in the project, normally inside node_modules/.
For example:
todo-app/
ā
āāā src/
ā
āāā package.json
ā
āāā node_modules/
āāā react/
āāā react-dom/
āāā @company/
ā āāā design-system/
āāā clsx/
āāā ...node_modules is, as we already know, the local collection of packages that the project needs.
It is not some kind of magical file system. They are real files: JavaScript, TypeScript declaration files, CSS, JSON, images, source maps, documentation, etc.
A package might have a structure like this:
@company/design-system/
āāā package.json
āāā dist/
ā āāā index.js
ā āāā index.d.ts
ā āāā Button.js
ā āāā Modal.js
ā āāā styles.css
āāā README.mdThe package.json contains information about the package, such as its name, version, dependencies, and entry points.
8. What actually happens when you import something?
If the application writes:
import { Button } from '@company/design-system'the build system needs to locate that package.
Conceptually:
TodoList.tsx
ā
ā import Button
ā¼
@company/design-system
ā
ā¼
package.json
ā
ā¼
dist/index.js
ā
ā¼
Button.jsThe package indicates where its entry code is located and which exports it provides.
Therefore, an import does not necessarily mean "download something from the Internet when the user clicks". During development and the build process, the tools resolve that import to the corresponding files.
In general, node_modules can become huge because dependencies have their own dependencies. For example:
todo-app
ā
āāā node_modules
āāā @company/design-system
ā
āāā react
ā
āāā react-dom
ā
āāā clsx
ā
āāā some-library
ā āāā dependencies...
ā
āāā ...Also, not everything in node_modules ends up in the browser.
There may be packages used exclusively during development:
typescript
vite
eslint
prettier
vitestThese help write, check, and build the application, but they do not necessarily become part of the JavaScript that the user eventually executes.
Therefore:
node_modulesdoes not mean "everything that gets sent to the browser".
It means:
"The packages that this project has installed as dependencies."
At this point, the Node.js and browser environments are often mixed together. We already know that the browser has an engine capable of executing JavaScript, and Node.js is another runtime, and it is commonly needed for the development environment and tooling:
Computer
ā
āāāāāāāāāāāāā“āāāāāāāāāāāā
ā ā
Node Browser
ā ā
npm / pnpm JavaScript engine
Vite DOM
TypeScript CSS
build tools UIFor example:
npm install
npm run dev
npm run buildnormally means running tools that operate on Node.js.
The browser appears later, when the application has already been built and its assets are served to the user.
Why do we need to transpile?
JavaScript is the language that the browser can execute directly, but developers often write using additional languages or syntax.
For example, TypeScript:
const age: number = 32The browser does not need or understand the : number annotation.
A transformation can produce:
const age = 32Similarly, JSX:
function App() {
return <h1>Hello</h1>
}is not JavaScript that the browser can directly interpret as JSX.
React tooling transforms JSX into JavaScript that uses React APIs.
Simplifying:
TypeScript
ā
JavaScript JSX
ā
JavaScriptThis type of transformation between languages or variants of a language is usually called transpilation.
Transpilation, compilation, and bundling are not exactly the same
These terms are often used informally, so it is easy to mix them up.
Transpilation consists, simplifying things, of transforming code from one form into another compatible form. For example:
TypeScript ā JavaScript
JSX ā JavaScriptCompilation is a broader concept: transforming code from one representation into another that the machine can understand in order to execute it. In the modern frontend world, it is often used informally to refer to the complete process of transforming source code into production code.
Bundling is another operation: bringing modules and their dependencies together into one or more files that can be used by the application.
For example:
main.tsx
ā
āāā App.tsx
ā āāā TodoList.tsx
ā
āāā api.ts
ā
āāā @company/design-system
āāā ButtonThis forms a dependency graph.
The bundler traverses that graph and generates the assets required by the application.
Therefore, a simplified way of imagining a modern build is:
Source code
ā
āāā TypeScript / JSX transformation
ā
āāā module resolution
ā
āāā bundling
ā
āāā optimization
ā
āāā minification
ā
ā¼
Production artifactsSo what exactly is the build?
The build is the complete process through which a project's source code is transformed into the files that can be used to run or deploy the application.
For example:
src/
āāā App.tsx
āāā TodoList.tsx
āāā main.tsx
ā BUILD
dist/
āāā index.html
āāā assets/
āāā index.js
āāā index.css
āāā ...The result can be called a build artifact or simply an artifact: the files produced by the build process.
In a typical frontend application, these can include:
HTML
JavaScript
CSS
images
fonts
other static assetsThese are the files that a web server or CDN can ultimately serve to the browser.
What happens to the Design System Button during the build?
This is one of the fundamental ideas.
Suppose we have:
import { Button } from '@company/design-system'
export function TodoList() {
return (
<div>
<h1>My Todos</h1>
<Button>New todo</Button>
</div>
)
}The application has:
todo-app/
āāā src/
ā āāā TodoList.tsx
ā
āāā node_modules/
āāā @company/
āāā design-system/
āāā dist/
āāā Button.jsDuring the build, the tooling follows the import and finds the required code.
Conceptually:
TodoList.tsx
ā
ā imports
ā¼
Design System
ā
ā¼
Button.js
ā
ā¼
Application buildThe final application contains the code necessary to execute that Button.
It is important to clarify that there is not necessarily a literal copy of Button.js as a separate file. The bundler may combine it with other modules, split it into chunks, optimize it, remove unused code, or apply other transformations.
But conceptually, it is perfectly valid to think:
The application incorporates the code necessary for the Button it consumed from the library into its build.
The browser does not need to know that this code originally came from @company/design-system. For the browser, what ultimately exists is executable JavaScript.
The Design System has its own build too
Here we reach a very important distinction: the library and the consuming application have different builds.
The Design System team might write:
Design System source
ā
āāā Button.tsx
āāā Modal.tsx
āāā Input.tsx
āāā ...and build it:
Design System source
ā
Design System build
ā
dist/
ā
package
ā
Package RegistryFor example:
@company/design-system@1.2.3Later, the Todo App consumes that package:
Todo App source
+
Design System package
+
React
+
other dependencies
ā
Todo App build
ā
dist/
ā
BrowserTherefore, there are two conceptually different processes:
DESIGN SYSTEM
TSX source
ā
build
ā
package
ā
registryand:
APPLICATION
TSX source
+
installed packages
ā
build
ā
production artifacts
ā
browser9. Publishing a new version
Suppose the Design System currently has:
@company/design-system@1.2.2The Button is modified and a new version is published:
@company/design-system@1.2.3The new package is built and published to the package registry:
Developer
ā
Git repository
ā
CI/CD
āāā tests
āāā build
āāā publish
ā
Package Registry
ā
@company/design-system@1.2.3The application that was using 1.2.2 does not automatically change simply because 1.2.3 now exists.
The application has to resolve or update its dependency and rebuild its own project in order to incorporate the new version.
Conceptually:
Todo App
ā
ā currently uses
ā¼
1.2.2
Design System publishes
ā
ā¼
1.2.3
Todo App
ā
ā update dependency
ā¼
1.2.3
ā
ā build
ā¼
new application artifactThis is important:
Publishing a library and deploying an application are different operations.
17. Versions and Semantic Versioning
Libraries often use some form of versioning, frequently Semantic Versioning:
1.2.3
ā ā ā
ā ā āāā patch
ā āāāāā minor
āāāāāāā majorSimplifying:
1.2.3 ā 1.2.4usually represents compatible fixes.
1.2.3 ā 1.3.0usually represents new compatible functionality.
1.2.3 ā 2.0.0usually indicates changes that may break the existing API.
This is especially important for a Design System library because many applications may depend on it simultaneously.
A seemingly small change to a component's API can affect multiple consumers.
18. package.json and lockfiles
An application might declare a dependency like this:
{
"dependencies": {
"@company/design-system": "^1.2.2"
}
}The ^ symbol allows certain compatible updates according to Semantic Versioning rules.
But the project will usually also have a lockfile, such as:
package-lock.json
pnpm-lock.yaml
yarn.lockThe lockfile records the exact versions that were resolved.
This allows different machines and environments to reproduce the same dependencies consistently.
Therefore, updating a dependency may involve changes both in:
package.jsonand:
pnpm-lock.yamlor the corresponding lockfile.
19. Storybook and the package have different destinations
A library can have two different technical products derived from the same code:
Design System repository
ā
āāāāāāāāā“āāāāāāāā
ā ā
Storybook Package
ā ā
ā ā
Hosting/web app Package Registry
ā ā
ā ā
Developers browse Apps installStorybook can be published at an internal URL so that developers can browse the components.
The package, on the other hand, is published to a package registry so that applications can install it.
They are two different things:
Storybook displays and provides a workspace for the components. The package allows them to be consumed from code.
20. The complete picture
The entire system can be summarized in this flow:
DESIGN SYSTEM
ā
āāāāāāāāāāāāāāā“āāāāāāāāāāāāāāā
ā ā
Design Code
rules ā
ā Component library
Figma ā
ā Storybook
ā ā
ā āāāāāāāāā“āāāāāāāā
ā ā ā
ā Storybook Package
ā hosting registry
ā ā ā
ā ā ā
ā Developers Applications
ā ā
ā npm/pnpm install
ā ā
ā ā¼
ā node_modules
ā ā
ā ā¼
ā Application build
ā ā
ā ā¼
ā HTML / JS / CSS
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā¼
BrowserThe fundamental idea is that there are two major phases.
During development:
Source code
ā
Packages
ā
node_modules
ā
Build toolsDuring execution:
Production artifacts
ā
Browser
ā
JavaScript / CSS / HTMLThe browser does not need to know what npm is, what node_modules is, where the package registry was, or who originally wrote a component.
All of those are concerns of the development and build process.
21. The mental model worth keeping
A good way to think about this entire ecosystem is:
A package is a way of distributing reusable code. A package registry is the place from which that code is distributed.
node_modulesis the local copy of the packages that a project has installed. A build transforms source code and its dependencies into artifacts that can be executed or served. Storybook provides an environment for developing, visualizing, documenting, and testing UI components. And a Design System is the broader concept that encompasses design decisions, patterns, components, documentation, and, usually, their technical implementations.
The complete journey of a component can therefore be imagined like this:
Developer writes Button
ā
Button exists inside Design System
ā
Storybook displays/tests Button
ā
Design System is built
ā
Package is published
ā
Package Registry stores version 1.2.3
ā
Todo App installs 1.2.3
ā
Package appears in node_modules
ā
Todo App imports Button
ā
Todo App is built
ā
Button's required code becomes part of
the application's production output
ā
HTML + CSS + JavaScript are served
ā
Browser executes the JavaScript
ā
User sees the ButtonThe library, therefore, is not a remote service that the browser queries to obtain a Button. It is reusable code that an application incorporates during its development and build process.
And that distinction ā library versus service, package versus application, build versus runtime ā is one of the fundamental foundations for understanding how modern frontend development is organized.