The Atlassian Problem
If you’ve ever used Atlassian’s products, you know the feeling. Jira looks one way, Confluence looks another, Bitbucket looks like a third team built it, because they did. Same company, same suite, same user navigating between them. Three different visual languages.
We were heading down the same path. At KUY.io we’ve been building a suite of devtooling products: a CI server, a project board, a story mapping tool, more in the pipeline. About a year ago we started a shared ViewComponent library so we’d at least have common building blocks. A Button component, a Card, a Modal. The idea was: share the components, get consistency for free.
It didn’t work. Or rather, it worked exactly as well as you’d expect a bag of components with no design language to work. Every product had the same Button component, but they’d use it differently. Different spacing around it, different assumptions about sizing, different ideas about when to use which variant. The components were shared. The design wasn’t.
We also hit extensibility problems. The original component library was built pragmatically: whatever the current product needed, we’d add. But as more products consumed it, every new feature request meant weighing one product’s needs against another’s. The components had accumulated product-specific assumptions that didn’t generalize.
So this week we started over. Sirius2: a proper design system, not just a component library.
What’s different this time
The fundamental change is that components don’t reference colors directly. They reference semantic tokens, names like “surface background” or “critical text,” which map to a palette through a theme layer. Three levels of indirection: component to semantic token to palette value.
This sounds over-engineered until you think about what it prevents. When a developer reaches for a background color, they don’t pick a hex value or even a named color. They pick a semantic role: “this is a surface,” “this is a warning state.” The design system decides what that looks like. And when we need a dark theme, or a different color temperature for a specific product, we swap the mapping layer without touching any component code.
The palette itself is generated using HSLuv, a perceptually uniform color space, so that our blue-3 and our red-3 actually look like they belong at the same lightness level. Fourteen color families, sixteen lightness steps each. Every variant is programmatically generated from a Ruby class, not hand-picked in a design tool.
The first week
I got the first set of components in within three days: Button, Banner, Icon, Spacer, Popover, Card, Link. Not because they’re simple (Popover alone needs Tippy.js integration, z-index management, and focus trapping) but because the architecture decisions were already made. The token system drives everything. Once you know how a component connects to the token layer, building it is mechanical.
The component base class uses a custom attribute DSL instead of standard ViewComponent initializers:
attribute :variant, :symbol, default: :default, only: %i[primary secondary tertiary plain]
attribute :disabled, :boolean, default: false
This handles type casting, enforces allowed values, and automatically forwards unrecognized attributes to the HTML element. It’s a small thing that saves a lot of boilerplate across sixty-plus components.
What I’m aiming for
I want our developers to be able to drop Sirius2 into a new Rails app and have it look like a KUY.io product without any custom CSS. Consistent typography, consistent spacing scale (mapped to a CSS custom property system), consistent shadows, consistent everything.
The documentation is as important as the components. Every component will have a metadata class that describes its props, slots, and shows live examples. Fire up the documentation app that renders all components and the philosophy of the design system into a browsable reference. I don’t want anyone guessing how a component works.
The architecture is right this time. Shared design decisions encoded in the system, not left to individual judgment calls. That’s the piece we were missing before. I’m excited to start rolling this out across our products.