Back to blog
The Component Library Behind Gryt
grytdesignopen-source

The Component Library Behind Gryt

Gryt's interface moved out of the client and into two npm packages that share one set of tokens, so the desktop app and the phone app can look like the same product.

Sivert·

Title: The component library behind Gryt
Date: 26 August 2026
Author: Sivert Gullberg Hansen
Keywords: design systems, component libraries, design tokens, React, React Native, theming

Abstract

By the summer Gryt had three surfaces that had to look like the same product: the desktop and web client, the Keycloak sign-in page and a React Native app. None of them could import a component from another. So the interface moved into two packages, @gryt/ui for the web and @gryt/ui-native for React Native. Both read their colours, spacing, radii and type from one theme in @gryt/theme. The web half has 47 components built on Base UI, and the docs site assembles whole screens from them. A theme is a couple of dozen hex values, small enough to be a link, and one of the 18 presets came from a community member through that link. Releases run on changesets. The library is chat-shaped on purpose, and its props still move.

1 Introduction

For most of Gryt's life the interface lived in one place: the client. A button was a <button> with some Tailwind on it, written where it was needed, and if the next screen needed a button too, that one got written where it was needed. That works fine until you have a second renderer.

By the summer I had three surfaces that had to look like the same product. The desktop and web client, the sign-in page that Keycloak serves, and a React Native app that was about to exist. Three codebases, none of which could import a div from the others. This post answers two questions:

  1. How do you make three renderers look like one product?
  2. How do you keep a shared library's releases from becoming a job of their own?

2 Design

2.1 Two packages, one theme

@gryt/ui is the web half, @gryt/ui-native is the React Native half, and both read their colours, spacing, radii and type from one theme object in @gryt/theme. The repository started on 30 June and is documented at ui.gryt.chat [1] (Figure 1).

The Gryt UI documentation site, a dark catalogue page listing 47 components with a search field, a theme picker, and cards for the example screensThe Gryt UI documentation site, a dark catalogue page listing 47 components with a search field, a theme picker, and cards for the example screens

Figure 1: The catalogue on ui.gryt.chat.

What the two packages share is the theme. Same token names, same values, same GrytProvider shape. A colour is --gryt-accent on the web and theme.accent on the phone, and both come out of the same file. When the theme learned to carry a font, it learned it once and both renderers picked it up.

2.2 The web half

47 components. They're built on Base UI [2], the MUI team's headless library, so the keyboard behaviour, focus trapping and ARIA wiring come from people who do that for a living, and the styling is Tailwind on the Gryt palette. There's no CSS-in-JS runtime. The stylesheet is compiled and you import it once.

A dialog that traps focus properly, a menu that responds to arrow keys, a combobox that announces itself: those are weeks of work each, and getting them subtly wrong is worse than not having them at all. Base UI ships them unstyled, and I style them.

2.3 The native half

@gryt/ui-native is where most of the work went this month, because React Native has no CSS and gives you a different set of primitives to fight. Sheets, drawers, sliders and swipe gestures all got rebuilt on react-native-gesture-handler [3] after the hand-rolled versions turned out to fight the scroll views inside them.

2.4 Themes are a link

A Gryt theme is a couple of dozen hex values. That's small enough to be a URL, which changes what a theme is for.

The generator [4] puts an editor on the left and real components on the right (Figure 2): not swatches but the actual Button and TextField and voice tile reading the theme you're building. When it looks right you press Copy link, and pasting that link into the client's appearance settings applies it.

The theme generator, with colour inputs and twelve-step neutral scales on the left and live Gryt components rendering the theme on the rightThe theme generator, with colour inputs and twelve-step neutral scales on the left and live Gryt components rendering the theme on the right

Figure 2: The theme generator, with live components on the right.

2.5 Releases

@gryt/ui and its siblings are on changesets [5]. A pull request that changes behaviour carries a small markdown file saying which packages it touches and whether it's a patch, minor or major. When that merges, a bot opens a "Version Packages" pull request that collects every pending changeset, writes the changelogs and bumps the versions. Merging that publishes to npm. 74 of those have merged so far.

3 Results

3.1 Whole screens from the library

The docs site carries whole screens built out of nothing but the library: the sign-in page, the settings dialog, a channel view, a voice room, the two left rails. Each runs on its own page and ships its source. They exist because a catalogue of buttons doesn't tell you whether the parts hold together. Figure 3 is one of them.

A full-screen chat panel example — channel header, message bubbles from three people, and a composer — assembled only from Gryt UI componentsA full-screen chat panel example — channel header, message bubbles from three people, and a composer — assembled only from Gryt UI components

Figure 3: A chat panel built only from Gryt UI components.

3.2 Presets

The library ships 18 presets, and every one is checked for contrast in both light and dark before it ships. Table 1 lists where fourteen of them come from.

Table 1: Where the presets come from.

SourcePresets
The owl avatar palettes, so a server's chrome can match the birds in its member listRose, Amber, Wine, Indigo, Forest, Ice
Ports of themes people already knowDracula, Nord, Catppuccin, GitHub, Claude, shadcn/ui, Solarized
A community memberGryt Rounded

Gryt Rounded is the shipped palette with every corner set to eight pixels, which mostly means the controls that were pills stop being pills. Carlo built it in the generator and sent me the link, I pasted it in, liked it, and it's a preset now (Figure 4). That loop working is the part I wanted from the generator, and I didn't expect it to happen within a week.

Gryt's appearance settings in dark mode, listing the Gryt, Gryt Rounded, Ember, Paper and Rose themes with colour strips beside each nameGryt's appearance settings in dark mode, listing the Gryt, Gryt Rounded, Ember, Paper and Rose themes with colour strips beside each name

Figure 4: Appearance settings in the client, with Gryt Rounded second.

3.3 Releases

Table 2 has where the packages stood. The version numbers are only interesting because before this, "what changed between the version the client uses and the one the phone uses" was a question I answered by reading commits.

Table 2: Package versions on 26 August 2026.

PackageVersion
@gryt/ui0.21.1
@gryt/ui-native0.13.4
@gryt/theme0.7.0

4 Discussion

4.1 Limitations

  • It isn't a general-purpose design system, and I wouldn't recommend building a dashboard on it. The primitives are chat primitives: message rows, member lists, voice tiles, a sheet that behaves on a phone. There's no data table, no date picker, no chart.
  • The versions start with a zero for a reason. Props still move. If you depend on it, pin it.

4.2 Licence

The packages are MIT, unlike the AGPL-3.0 apps that use them. If you want a chat-shaped component library for something that isn't Gryt, take it.

5 Conclusion

How do you make three renderers look like one product? Give them one theme, with the same token names and values, and let each renderer have its own components on top. Then build whole screens from the library to prove the parts hold together.

How do you keep releases from becoming a job? Let each pull request say what it changes, and let a bot collect that into versions and changelogs.

The full catalogue, with every component's props and a copy button on each snippet, is at ui.gryt.chat. The avatars have their own page there, and their own post.

References

  1. Gryt UI. https://ui.gryt.chat
  2. Base UI. https://base-ui.com
  3. react-native-gesture-handler. https://github.com/software-mansion/react-native-gesture-handler
  4. Gryt UI. Theme generator. https://ui.gryt.chat/theme/generator
  5. Changesets. https://github.com/changesets/changesets