Skip to content
touchcn
Esc
navigateopen⌘Jpreview
Blog

Introducing touchcn

A shadcn-style mobile UI component library that renders Material 3 on Android and iOS liquid glass on iOS — one component set, and you own the code. Here is the motivation, the architecture, and where it is headed.

By Robin Genz

I have spent a lot of years building mobile apps with web technology, and for most of that time the component layer was a framework I imported and lived with. That model works right up until it doesn’t: you hit a component that renders almost the way you need it to, and there is no seam to get inside. You fork, you wrap, you override with ever more specific selectors, or you give up on the detail. The component is a black box, and the box is not yours.

touchcn is my attempt at the opposite trade. It is a shadcn-style component library for mobile web and cross-platform apps: instead of shipping opaque components from an npm package, the CLI copies styled component source into your repository. You own every line from the moment you add it. Need a different animation curve, an extra slot, a tweak to the markup? Edit the file. There is no fork, no wrapper, no override war — it is just your code.

This post is an honest description of what touchcn is today and where it is going, not a launch pitch.

Why build another component library?

The motivation is design-language flexibility. A mobile component should feel native to the platform it runs on. On Android that means Material Design 3 — tonal surfaces, elevation, the Material You palette. On iOS it means the iOS 26 liquid-glass look — frosted, translucent chrome that layers over content. Most cross-platform component sets pick one look and approximate the other, or hand you a neutral style that belongs to neither.

I wanted a set where the same component renders authentically on both, and where switching is a runtime concern, not a build-time fork in my application code. And I wanted the shadcn ownership model underneath it, because platform-authentic UI is exactly the kind of thing you end up wanting to tweak.

The architecture in brief

touchcn is split into two halves with a hard boundary between them.

The engine is a set of versioned npm packages you depend on but never copy: @touchcn/core (framework-agnostic platform detection, the AppearanceManager, Material You palette generation), @touchcn/tailwind (a Tailwind v4 CSS-first token layer that holds all the platform styling), and the per-framework glue layers @touchcn/angular, @touchcn/react, and @touchcn/vue.

The registry is the styled component source the CLI copies into your project. Those files are deliberately thin: markup, class strings, and variant maps. Anything with real behavior — measurement, focus trapping, scroll locking, ripple — lives in the engine packages and is composed in, so it can still be fixed with an npm update after you have copied the component.

The invariant that makes it all work is what I call the theme-dumb principle: components carry no platform conditionals. The AppearanceManager toggles .ios / .md (and .dark) classes on the <html> element, and every platform difference is expressed as CSS scoped to those classes in the Tailwind token layer. A button is just a button; whether it looks like Material or like glass is decided entirely by which class is on the root. That keeps both framework ports rendering from a single styling source of truth, and it is why the same component can re-skin itself at runtime with no JavaScript branching.

One rule falls out of this and is worth calling out: no backdrop-filter under any .md scope. Glass blur is iOS-only. Beyond being a design choice, backdrop-filter creates containing blocks that break fixed-position overlays, so Material surfaces use opaque tonal color and elevation instead.

What works today

  • Angular, React, and Vue are all supported, with a matching component set on each. React leans on Radix and Vue on Reka UI for headless behavior where Angular uses its own primitives, but all three emit the same tcn-* classes and render from the same token layer.
  • A broad set of mobile components — page, navbar, tabbar, list, card, button, badge, input, switch, bottom sheet, action sheet, dialog, modal, drawer, popover, toast, banner, and more — 40 in total per framework.
  • Blocks — pre-composed screens (login, signup, onboarding, OTP, paywall, profile, search, settings, filters) that compose the components into ready-made pages you copy and reshape.
  • A CLI (npx touchcn add <component>) that copies sources, resolves registry dependencies, and installs the npm packages each component needs. Copied source doesn’t go stale, either: touchcn diff previews upstream changes and touchcn update applies them with a three-way merge that preserves your local edits.
  • Material You dynamic color generated from a seed, with cascade-correct dark switching.

What is next

  • More components and blocks, filling out the set as real apps put pressure on it.
  • Hardening the token system, so custom themes and edge cases stay robust.
  • Continued polish on both platform looks as the native design languages keep evolving.

If the ownership model of shadcn and the platform-authentic look of native mobile UI both appeal to you, touchcn is aiming squarely at that intersection. It is rough today, but the shape is there. Follow along on GitHub.

— Robin Genz