@anguszhu/kroma-ui

A small, themeable UI component library with React and Vue support.
513
Install
npm install @anguszhu/kroma-ui
Latest Version:0.2.1
Vue:3
License:MIT
Last Updated:Sep 17, 2026
Links: npm
Maintainer: anguszhu

Kroma

A small, themeable component library — buttons, dialogs, toasts, switches, selects, and a few other primitives, extracted from a real production app and generalized for reuse. Ships idiomatic Vue 3 and React implementations from the same package, sharing CSS and framework-agnostic logic (focus trapping, dismiss timers) so the two stay in lockstep.

  • Vue 3 <script setup>, or React 18+/19 — both fully typed, both idiomatic to their framework (v-model in Vue, controlled props in React) rather than a lowest-common-denominator API.
  • Themeable by design — every color, spacing and radius is a CSS custom property (--kr-*). Override any of them at :root or on a wrapper element to reskin the whole library; nothing is hardcoded. The same stylesheet styles both frameworks identically.
  • Light/dark for free via native CSS light-dark() — components follow color-scheme, no dark-mode class toggling required.
  • No icon font dependency. Components that need an icon (search, clear, toast status) ship a small inline SVG default and expose a slot (Vue) or a ReactNode prop (React) to replace it with your own.
  • Tree-shakeable — ESM + CJS builds, each component is a separate export.
  • Minimal dependencies — vue and react/react-dom are the only peer dependencies, and each is optional: installing Kroma for a React project never pulls in Vue, and vice versa.

Requirements

Kroma's default theme uses light-dark() and color-mix(), so it targets evergreen browsers (Chrome 123+, Safari 17.5+, Firefox 120+). vue (^3.4.0) and react/react-dom (^18.0.0 || ^19.0.0) are both optional peer dependencies — install whichever framework you're using.

Install

# Vue
npm install @anguszhu/kroma-ui vue

# React
npm install @anguszhu/kroma-ui react react-dom

Usage

Import the stylesheet once (usually in your app's entry point) — it's shared by both frameworks:

import '@anguszhu/kroma-ui/style.css'

Vue

<script setup lang="ts">
import { ref } from 'vue'
import { KrButton, KrDialog, KrTextField } from '@anguszhu/kroma-ui/vue'

const showDialog = ref(false)
const name = ref('')
</script>

<template>
  <KrButton @click="showDialog = true">Rename</KrButton>

  <KrDialog
    v-model:show="showDialog"
    title="Rename bookmark"
    :actions="[{ text: 'Save', action: () => save() }]"
  >
    <KrTextField v-model="name" label="Name" autofocus />
  </KrDialog>
</template>

React

import { useState } from 'react'
import { KrButton, KrDialog, KrTextField } from '@anguszhu/kroma-ui/react'

export function RenameButton() {
  const [showDialog, setShowDialog] = useState(false)
  const [name, setName] = useState('')

  return (
    <>
      <KrButton onClick={() => setShowDialog(true)}>Rename</KrButton>

      <KrDialog
        show={showDialog}
        onShowChange={setShowDialog}
        title="Rename bookmark"
        actions={[{ text: 'Save', action: () => save() }]}
      >
        <KrTextField value={name} onValueChange={setName} label="Name" autofocus />
      </KrDialog>
    </>
  )
}

Prop names carry the same meaning across both frameworks, translated to each framework's own idiom: Vue's v-model:show / @update:show pairs with React's show / onShowChange; Vue's provide/inject (used internally by KrSelectGroup) pairs with a React Context. Component and prop names otherwise match 1:1.

Components

Component Description
KrButton primary / secondary / link button variants.
KrOverlay Fixed-position scrim + content slot with enter/leave transitions. Building block for dialogs, menus and popovers.
KrDialog Modal dialog built on KrOverlay — title, body text, a default slot for arbitrary form content, and an actions array. Handles Escape-to-close and Tab focus trapping.
KrSection A titled card/section wrapper.
KrSelectGroup / KrSelectItem Single-select radio group with default / color / icon / card / text visual variants, and shapes square / circle / rounded. Use KrSelectItem standalone or let the group render items from a data array.
KrSwitch Visual toggle switch (presentational — pair it with a native checkbox, the way KrToggleRow does, for accessible standalone use).
KrToggleRow An accessible label + native checkbox + KrSwitch, with optional title/subtitle.
KrTextField Labeled text input with an error state (shake animation + red outline).
KrSearchInput Pill-shaped search input with a clear button; label / clearLabel props for i18n.
KrToast Auto-dismissing status toast with an optional action button and countdown.
KrEmpty Empty-state placeholder: icon slot, title, description.
KrMenuItem A single menu row (or type="divider"), with an optional icon slot/prop.
KrChevron A tiny CSS-only directional triangle, handy as a disclosure indicator.

Every component's props and emitted events are typed — import the exported Kr* types (e.g. KrSelectItemData, KrDialogAction) from @anguszhu/kroma-ui/vue or @anguszhu/kroma-ui/react alongside the components for full autocomplete.

Theming

Everything reads from a small set of semantic tokens defined in @anguszhu/kroma-ui/style.css (see src/styles/tokens.css in this repo). To restyle, override the tokens you care about — no build step, no config:

:root {
  --kr-theme: #7c3aed;          /* your brand accent */
  --kr-theme-secondary: #a78bfa;
  --kr-surface: #ffffff;
  --kr-spacing-3: 20px;
  --kr-font-family: "Inter", -apple-system, system-ui, sans-serif;
}

Every component sets its own font-family: var(--kr-font-family) rather than relying on inheriting whatever font the host page happens to have in scope at that point in the DOM — the default is a system-font stack, so components look right out of the box even before you override it.

Because the accent tokens are defined with light-dark(), you can supply a dark-mode value in the same declaration: --kr-theme: light-dark(#7c3aed, #c4b5fd);. Pair that with color-scheme: light dark (already set by the [data-kr-color-scheme="auto"] selector Kroma ships) to switch automatically with the OS.

Development

npm install
npm run test              # vitest — core logic + every component, both frameworks
npm run typecheck         # vue-tsc (src/vue + src/core) and tsc (src/react + src/core)
npm run build             # type declarations + ESM/CJS bundles for both entries + style.css
npm run storybook:vue     # preview the Vue components at http://localhost:6006
npm run storybook:react   # preview the React components at http://localhost:6007

Source is split into src/core (framework-agnostic logic and types, not published on its own), src/vue, and src/react — each component gets an idiomatic, hand-authored implementation per framework, sharing one CSS file per component (src/styles/components/) so the two can never visually drift apart. Each component has a colocated *.stories.ts/*.stories.tsx file. Storybook itself, @storybook/*, .storybook*/, and *.stories.* files are dev-only tooling — package.json's files field publishes only dist/, so none of it ships to npm. npm run build-storybook:vue / npm run build-storybook:react produce static storybook-static/ sites if you want to deploy a hosted preview separately.

What's not in here

This package intentionally ships only generic, product-agnostic UI. Anything tied to a specific app's domain model (e.g. a bookmark tree, browser-specific favicon fetching) stays in that app rather than leaking into a shared component library.

Related Packages

@bridge-ui/adapters

Ready-made date, icon, and i18n adapters for Bridge UI.

874 0
@any-design/anyui

A cute multi-framework UI component library

4,478 26
@codeenvision/ui-webcomponents

Cluster Envision reusable Web Components built on top of the CE UI framework.

5,250
@clusterenvision/ui-webcomponents

Cluster Envision reusable Web Components built on top of the CE UI framework.

1,776
@arcanalabs/ui-components

Arcana Labs UI Components — a typed, framework-native shadcn-style component lib...

5,735 3

Version History

Version Released Vue License
0.2.1 ^3.4.0 MIT
0.2.0 ^3.4.0 MIT
0.1.0 ^3.4.0 MIT