Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/button-shadcn-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'rowkit': minor
'@rowkit/tokens': patch
---

**Breaking (Button).** Variants are now `default` | `outline` | `secondary` | `ghost` | `destructive` | `link` — soft-ink solid is the default (omit `variant` or pass `default`). `primary` and `danger` are removed; soft `destructive` replaces solid danger. Size scale is `default` | `xs` | `sm` | `lg` | `icon` | `icon-xs` | `icon-sm` | `icon-lg`; the `icon` boolean prop is gone. Former bordered `secondary` is now `outline`; `secondary` is a muted fill (`surface-active`). Soft ink solid lightened to `oklch(0.26…)`. Link focus stays typographic (ring only). Dialog Cancel convention is `ghost` so soft Delete wins hierarchy.

**ButtonGroup.** New `ButtonGroup` joins related buttons with shared edges (`orientation` horizontal | vertical). Nested groups use a clear gap.

**Tokens.** Default primary is warm espresso graphite (`oklch(0.31 0.038 48)` / `#402a1f`), not near-black. Soft destructive wash in dark mirrors light (coloured label on a quiet red tint). Link focus is underline-only.
5 changes: 5 additions & 0 deletions .changeset/consistency-pass.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'rowkit': patch
---

**Consistency.** Select invalid focus uses `has-[:focus-visible]` (focus is on the inner input). FilterBar chip-remove focus uses the solid ring recipe. FilterBar Clear maps Button size to the bar (`default` at `md`, `sm` at `sm`). Field `size` inherits to nested Input/Select when they omit their own. DataTable gains `emptyReason` for the built-in empty state.
5 changes: 5 additions & 0 deletions .changeset/docs-home.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'rowkit': patch
---

**Docs.** New homepage composition: espresso mark/logo, branded hero, and a live Users FilterBar + DataTable + Pagination preview. README screenshots refreshed (`home.png`, `datatable-page.png`); outdated “twelve components” copy removed.
9 changes: 9 additions & 0 deletions .changeset/docs-layer-order-and-roadmap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'rowkit': patch
---

**Docs cascade fix.** Every heading on every docs content page was rendering at body size and weight. Wrapping VitePress's CSS in `@layer vp-theme` left the layer order to first-appearance, and the wrapped CSS lands at the top of the bundle — so `vp-theme` sorted _below_ Tailwind's `base`, and preflight's `h1`–`h6` reset (`font-size: inherit`) beat every VitePress heading rule, since a layer beats specificity. The order statement now rides on the wrapped CSS itself (`@layer theme, base, vp-theme, components, utilities`), which puts `vp-theme` above `base` so headings survive and below `utilities` so live demos still win. `docs-styles.test.ts` now asserts both bounds instead of only the lower one.

**Docs homepage.** The bulk-actions bar moved below the table. Above it, every checkbox tick inserted or removed a band and shoved the table under the cursor — the row you were aiming at moved because you selected the one before it. The demo roster grew to 80 people so the money shot pages through real data — ten rows a page across seven pages, instead of a single page of six — and narrowing a filter now returns to page 1.

**`NEXT.md` is now `ROADMAP.md`**, rewritten as a plan of record: current state, what 1.0 actually requires, and what stays out of scope. The docs page moves from `/next` to `/roadmap`.
5 changes: 5 additions & 0 deletions .changeset/espresso-deeper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rowkit/tokens': patch
---

**Tokens.** Default espresso primary nudged darker: `oklch(0.31 0.038 48)` / `#402a1f` (was `0.33` / `#462f24`).
5 changes: 5 additions & 0 deletions .changeset/safari-docs-cascade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'rowkit': patch
---

**Safari / docs demos.** VitePress theme CSS is wrapped in `@layer vp-theme` so Tailwind utilities beat its form/table reset without `all: revert-layer` (broken in Safari). DemoBox isolates markdown-table chrome on `.rk-demo`. DataTable keeps `h-*` on cells (`min-height` is ignored for `table-cell`). Input/Select use `leading-normal` for Safari text centering.
6 changes: 6 additions & 0 deletions .changeset/select-check-quiet-borders.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'rowkit': patch
'@rowkit/tokens': patch
---

Select shows a trailing checkmark on the selected option (shadcn-style) instead of a left indicator with a selected fill. Secondary buttons use a quiet `border-input` outline on a card surface. Dialog footers keep a hairline divider with Cancel as secondary. Resting control borders (`input`) are quieter. Focus is soft silver (`ring` → `gray-708` light / soft white dark) — border + translucent outer ring, not an ink halo.
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ body:
attributes:
label: Scope check
options:
- label: I have read [Considered, not planned](https://github.com/NikolaiKushner/rowkit/blob/main/ROADMAP.md#considered-not-planned) and this is not on that list.
- label: I have read [what's next / out of scope](https://github.com/NikolaiKushner/rowkit/blob/main/NEXT.md) and this is not on that list.
required: true
- label: I understand rowkit is twelve components by design, and that a new component is unlikely to be accepted.
- label: I have described the problem first — a new component is welcome when it earns a place on a data-dense surface.
required: true

- type: textarea
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ storybook-static/
npm-debug.log*
pnpm-debug.log*
plan.md
.visual-check/

# VitePress build output and cache
docs/.vitepress/dist
Expand Down
Binary file added .pnpm-store/v11/index.db
Binary file not shown.
12 changes: 12 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ const config: StorybookConfig = {
name: '@storybook/vue3-vite',
options: {},
},
// Brand assets + fonts for the manager chrome (preview loads fonts via CSS).
staticDirs: [
'../docs/public',
{
from: '../node_modules/@fontsource-variable/geist',
to: '/fontsource/geist',
},
{
from: '../node_modules/@fontsource-variable/geist-mono',
to: '/fontsource/geist-mono',
},
],
core: { disableTelemetry: true },
/**
* Two plugins the preview cannot build without.
Expand Down
16 changes: 16 additions & 0 deletions .storybook/manager-head.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<link rel="icon" type="image/svg+xml" href="/mark.svg" />
<meta name="theme-color" content="#402a1f" />
<link rel="stylesheet" href="/fontsource/geist/wght.css" />
<link rel="stylesheet" href="/fontsource/geist-mono/wght.css" />
<style>
/* Sidebar brand — wordmark stays readable, not a billboard. */
.sidebar-header a {
display: flex;
align-items: center;
}
.sidebar-header img {
max-height: 24px;
width: auto;
display: block;
}
</style>
9 changes: 9 additions & 0 deletions .storybook/manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { addons } from 'storybook/manager-api'
import theme from './theme'

addons.setConfig({
theme,
sidebar: {
showRoots: true,
},
})
7 changes: 7 additions & 0 deletions .storybook/preview.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@
* @source is needed because the stories and components are read from source
* here rather than from the built bundle that dist/styles.css points at.
*/
@import '@fontsource-variable/geist/wght.css';
@import '@fontsource-variable/geist-mono/wght.css';
@import 'tailwindcss';
@import '@rowkit/tokens/css';

@source '../packages/ui/src';

html,
body {
font-family: var(--font-sans);
}
14 changes: 13 additions & 1 deletion .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Preview } from '@storybook/vue3-vite'
import theme from './theme'
import './preview.css'

/**
Expand All @@ -9,6 +10,12 @@ import './preview.css'
const preview: Preview = {
parameters: {
controls: { matchers: { color: /(background|color)$/i, date: /Date$/i } },
docs: { theme },
options: {
storySort: {
order: ['Patterns', 'Foundations', 'Data', 'Overlay', '*'],
},
},
a11y: {
// Fail the story rather than reporting quietly in a panel. Definition of
// done says zero violations, which only means something if it is a gate.
Expand All @@ -34,9 +41,14 @@ const preview: Preview = {
(story, context) => {
const theme = context.globals.theme === 'dark' ? 'dark' : 'light'
document.documentElement.classList.toggle('dark', theme === 'dark')
// Paint the iframe body too — otherwise dark screenshots show a white
// page around a short story root and look broken.
document.body.style.background = 'var(--color-background)'
document.body.style.margin = '0'
document.body.style.minHeight = '100vh'
return {
components: { story },
template: `<div class="bg-background text-foreground p-6"><story /></div>`,
template: `<div class="min-h-[100vh] bg-background font-sans text-foreground p-6"><story /></div>`,
}
},
],
Expand Down
48 changes: 48 additions & 0 deletions .storybook/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { create } from 'storybook/theming'

/**
* Storybook manager chrome, tuned to rowkit's tokens.
*
* Warm espresso primary, quiet neutrals, Geist — the same restraint as the
* components. Not a second brand for the workshop.
*/
export default create({
base: 'light',

// Warm espresso — primary-800. Clearly brown-graphite, not near-black.
colorPrimary: '#402a1f',
colorSecondary: '#402a1f',

// Surfaces — gray-988 page, white card, gray-940 hairline.
appBg: '#F7F8FA',
appContentBg: '#FFFFFF',
appPreviewBg: '#F7F8FA',
appBorderColor: '#E4E7EC',
appBorderRadius: 6,

// Type
fontBase: '"Geist Variable", Geist, ui-sans-serif, system-ui, sans-serif',
fontCode: '"Geist Mono Variable", "Geist Mono", ui-monospace, monospace',
textColor: '#1A1D21',
textMutedColor: '#6B7280',
textInverseColor: '#F7F8FA',

// Toolbar
barBg: '#FFFFFF',
barTextColor: '#6B7280',
barSelectedColor: '#402a1f',
barHoverColor: '#402a1f',

// Controls
inputBg: '#FFFFFF',
inputBorder: '#9AA3AD',
inputTextColor: '#1A1D21',
inputBorderRadius: 6,

// Brand
brandTitle: 'rowkit',
brandUrl: 'https://rowkit.dev',
// Wordmark lockup — mark + “rowkit”. Served from docs/public via staticDirs.
brandImage: '/logo.svg',
brandTarget: '_self',
})
13 changes: 11 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ Instructions for coding agents working **on** rowkit.
with a public surface. Prop naming, state ownership, event and slot shapes and
the recurring accessibility patterns are decided there, once, for every
component.
- **[`ROADMAP.md`](./ROADMAP.md)** — before proposing a component. The scope is
twelve, and that is a decision rather than a stage.
- **[`ROADMAP.md`](./ROADMAP.md)** — the plan of record: current state, what 1.0
requires, what is out of scope. No fixed component count; add surface when it
earns its place.

## Commands

Expand All @@ -29,6 +30,10 @@ pnpm typecheck # vue-tsc, strict
pnpm format # prettier
pnpm size # bundle budget, brotli

pnpm storybook # then, in another terminal:
pnpm visual:check # screenshot default stories, light + dark → .visual-check/
pnpm visual:check Button # scoped to one component

pnpm docs:props # regenerate the props tables after touching a prop or its JSDoc
pnpm docs:agents # regenerate packages/ui/AGENTS.md, likewise
```
Expand All @@ -37,6 +42,10 @@ pnpm docs:agents # regenerate packages/ui/AGENTS.md, likewise
the type checker all resolve `rowkit` through `packages/ui/dist`, and an unbuilt
workspace produces a wall of confusing type errors rather than one clear one.

After any change that touches variants, tokens, layout, or dark mode: run
`pnpm visual:check`, **Read the PNGs**, and fix what looks wrong before claiming
done. Styling fails silently — screenshots are how agents catch it.

## Three things that are true here and not everywhere

**Backward compatibility matters.** rowkit is a published package with semver and
Expand Down
19 changes: 10 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,21 @@ Repository: `github.com/NikolaiKushner/rowkit`
Package: `rowkit` on npm
Docs: `rowkit.dev`

Read `ROADMAP.md` before proposing new components. The scope is fixed at twelve components for v1.0.
Read `ROADMAP.md` before proposing work. It is the plan of record: where the
library stands, what 1.0 requires, and what is deliberately out of scope. New
surface earns its place — there is no fixed component count.

**Visual direction:** restraint, structure without severity, no excess. Neutral chrome; warm-espresso primary (`oklch(0.31 0.038 48)`), not a chromatic shout. Consumers rebrand via tokens — defaults must not fight them. Geist stays.

Read `docs/conventions.md` before designing a component API. Prop naming, state ownership, event and slot shapes, and the recurring accessibility patterns are decided there, once, for every component.

---

## Stack

- **Vue 3.5+** — Composition API, `<script setup>`, TypeScript strict mode
- **Reka UI** — accessible primitives. NEVER hand-roll focus management, ARIA wiring, or keyboard handling when a Reka primitive exists.
- **Tailwind CSS v4** — configured via the `@theme` block in CSS. There is no `tailwind.config.js`.
- **cva** (class-variance-authority) + **tailwind-merge** for variant management
- **Vite library mode** + **vite-plugin-dts** for declaration files
- **Vitest** + **Storybook 10** (`@storybook/addon-vitest`, `@storybook/addon-a11y` as a gate, not a panel). Storybook 10, not 9: `@storybook/vue3-vite@9` peers on Vite 7 and this repo is on Vite 8.
- **VitePress** for documentation
- **pnpm workspaces** — monorepo with `packages/tokens` and `packages/ui`

---

Expand Down Expand Up @@ -57,7 +56,7 @@ components/ComponentName/

## Definition of done for a component

A component is not finished until all seven are true:
A component is not finished until all eight are true:

1. Renders all variants correctly in light and dark mode
2. Full keyboard support, and that support is documented
Expand All @@ -66,16 +65,18 @@ A component is not finished until all seven are true:
5. Stories cover every variant and every state
6. Interaction test for the primary behavior
7. Docs page written, including a **"when not to use"** section
8. **Visual QA:** `pnpm visual:check <Component>` (Storybook must be running), then **Read the PNGs** and fix anything that looks wrong in light or dark. Green tests are not enough.

---

## How to work with me on this

- **One component per session.** Don't start a second component before the first meets the definition of done.
- **One concern per session.** Don't start a second polish cluster before the first is done.
- **API before implementation.** When I give you a prop interface, build to it exactly. If you think the API is wrong, say so before writing code rather than silently changing it.
- **Ask before adding dependencies.** Every dependency is a maintenance cost and a bundle-size cost.
- **Don't scaffold ahead.** No placeholder files for components we haven't started. Empty stubs rot.
- **When reviewing, list problems without fixing them** unless I ask. I want to decide what matters.
- **Look at the pixels.** After UI changes, screenshot and inspect. Do not claim "looks fine" from code alone.

---

Expand All @@ -85,6 +86,6 @@ Don't re-litigate these:

- **npm package, not copy-paste distribution.** shadcn-vue's model is deliberate and good, but rowkit ships as a versioned package.
- **Reka UI, not shadcn-vue as a dependency.** shadcn-vue is a reference implementation to learn from, not something rowkit installs.
- **Twelve components.** See `ROADMAP.md`.
- **No fixed component count.** Add components when they earn a place on a data-dense surface; do not invent for breadth.
- **MIT license.**
- **Tokens as a separate package**, so they can be consumed without importing components.
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# rowkit

[![npm](https://img.shields.io/npm/v/rowkit?color=3b5bdb)](https://www.npmjs.com/package/rowkit)
[![license](https://img.shields.io/npm/l/rowkit)](./LICENSE)
[![bundle size](https://img.shields.io/bundlejs/size/rowkit)](https://bundlejs.com/?q=rowkit)
<p align="center">
<img src="docs/public/logo.svg" alt="rowkit" width="200" />
</p>

Vue 3 components for data-dense interfaces — tables, filters, and the states around them.
<p align="center">
<a href="https://www.npmjs.com/package/rowkit"><img src="https://img.shields.io/npm/v/rowkit?color=402a1f" alt="npm" /></a>
<a href="./LICENSE"><img src="https://img.shields.io/npm/l/rowkit" alt="license" /></a>
<a href="https://bundlejs.com/?q=rowkit"><img src="https://img.shields.io/bundlejs/size/rowkit" alt="bundle size" /></a>
</p>

Twelve components, built on [Reka UI](https://reka-ui.com), typed against your row.
Vue 3 components for data-dense interfaces — tables, filters, and the states around them. Built on [Reka UI](https://reka-ui.com), typed against your row.

**[Documentation](https://rowkit.dev)** · **[Storybook](https://storybook.rowkit.dev)** · **[Roadmap](./ROADMAP.md)**

Expand All @@ -16,14 +20,16 @@ Twelve components, built on [Reka UI](https://reka-ui.com), typed against your r
page — which is the surface this image exists for.
-->

![A rowkit DataTable being filtered, sorted and paged](https://raw.githubusercontent.com/NikolaiKushner/rowkit/main/docs/public/rowkit-demo.gif)
![rowkit docs homepage — brand, CTAs, and a live Users table](https://raw.githubusercontent.com/NikolaiKushner/rowkit/main/docs/public/home.png)

## Why another component library

General-purpose kits handle the easy eighty per cent extremely well — buttons, inputs, cards. Then you build a users admin page, and none of it helped with the part that actually took the week: a sortable table that stays fast at ten thousand rows with column keys typed against your row, a filter bar that makes applied state obvious, and loading, empty and no-results states that agree with each other.

rowkit is that part, done once.

![A filtered, sorted Users table with selection — the rowkit money shot](https://raw.githubusercontent.com/NikolaiKushner/rowkit/main/docs/public/datatable-page.png)

## Install

```bash
Expand Down Expand Up @@ -91,7 +97,7 @@ The table reports the sort and renders what it is handed — it never reorders i

## What rowkit is not

A general-purpose UI kit. If you need forty components covering every case, [Nuxt UI](https://ui.nuxt.com) and [shadcn-vue](https://www.shadcn-vue.com) are better answers — and rowkit composes with either, since all three build on Reka UI. The scope is a decision, not a limitation; the full list, including what was deliberately left out, is in [ROADMAP.md](./ROADMAP.md).
A general-purpose UI kit. If you need forty components covering every case, [Nuxt UI](https://ui.nuxt.com) and [shadcn-vue](https://www.shadcn-vue.com) are better answers — and rowkit composes with either, since all three build on Reka UI. The scope is a decision, not a limitation; see the [roadmap](./ROADMAP.md) for what is deliberately left out and what comes next.

## For coding agents

Expand All @@ -112,6 +118,7 @@ pnpm dev # playground app
pnpm storybook # component workshop
pnpm test # unit, component and browser tests
pnpm docs:dev # documentation site
pnpm docs:shots # refresh README homepage screenshots (docs:dev must be running)
```

## License
Expand Down
Loading