uxonfly-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Phanindhra Kondru
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # UXonFly
2
+
3
+ > An open-source design system for AI coding sessions.
4
+ > Strong opinions. MIT licensed.
5
+
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+
8
+ ---
9
+
10
+ ## What this is
11
+
12
+ Every AI coding session starts blind. Generic shadcn components. Default
13
+ Tailwind colors. Modal-inside-a-modal. Empty states with no CTA. Errors in
14
+ toasts that should be inline. Destructive actions with single-click delete.
15
+
16
+ UXonFly is a curated set of design and UX rules — tokens, components, UX
17
+ patterns, flows, copy rules, and principles — that your AI reads before
18
+ generating UI. The rules live in one markdown file: `uxonfly.md`.
19
+
20
+ **Two ways to use it. Same content, same rules:**
21
+
22
+ 1. **As a file** — copy `uxonfly.md` into your project. Your AI reads it natively every session. Zero install.
23
+ 2. **As an MCP server** — run `uxonfly-mcp` and your AI invokes 7 tools (`get_component`, `get_ux_pattern`, …) on demand.
24
+
25
+ The file install is the recommended starting point. The MCP server is the
26
+ upgrade for power users who want tool-call precision.
27
+
28
+ MIT licensed. Fork it. Improve it. Send a PR.
29
+
30
+ ## What's in it
31
+
32
+ - **5 token categories** — colors, typography, spacing, shape, shadows
33
+ - **6 components** — button, input, card, badge, modal, toast
34
+ - **9 UX patterns** — navigation, modals, forms, loading, empty states, errors, destructive actions, data tables, notifications
35
+ - **3 flows** — empty-state, onboarding, auth
36
+ - **Copy rules** — voice, tense, errors, CTAs, tooltips, placeholders, labels
37
+ - **7 principles** — accessibility first, one primary action, error prevention, progressive disclosure, copy-driven, keyboard navigable, trust the system
38
+
39
+ Strong opinions. Border-first. Modern SaaS aesthetic (Linear / Stripe / Vercel-aligned).
40
+
41
+ ---
42
+
43
+ ## Install option 1 — file (recommended, 30 seconds)
44
+
45
+ This is the only step you need.
46
+
47
+ ```bash
48
+ # Download the rulebook
49
+ curl -O https://raw.githubusercontent.com/Phanikondru/uxonfly-mcp/main/uxonfly.md
50
+
51
+ # For Claude Code:
52
+ mv uxonfly.md CLAUDE.md
53
+
54
+ # OR for Cursor:
55
+ mv uxonfly.md .cursorrules
56
+ ```
57
+
58
+ Open your AI tool and start prompting. It will read the file before generating
59
+ UI on every session.
60
+
61
+ That's it. No npm install. No config. No login.
62
+
63
+ ---
64
+
65
+ ## Install option 2 — MCP server (power users)
66
+
67
+ If you want your AI to invoke specific tools (`get_component`,
68
+ `get_ux_pattern`, `get_tokens`, …) on demand instead of reading the whole
69
+ file every prompt, run UXonFly as a local MCP server.
70
+
71
+ > **Status**: `uxonfly-mcp` is not yet published to npm. The one-line `npx`
72
+ > install will work after the first publish — until then, use the local-build
73
+ > path below.
74
+
75
+ ### Install today (local build)
76
+
77
+ ```bash
78
+ git clone https://github.com/Phanikondru/uxonfly-mcp.git
79
+ cd uxonfly-mcp
80
+ npm install
81
+ npm run build
82
+ ```
83
+
84
+ Then point your AI tool at the compiled binary using its absolute path.
85
+
86
+ **Claude Code:**
87
+
88
+ ```bash
89
+ claude mcp add uxonfly -- node /absolute/path/to/uxonfly-mcp/build/index.js
90
+ ```
91
+
92
+ **Cursor** — add to `~/.cursor/mcp.json`:
93
+
94
+ ```json
95
+ {
96
+ "mcpServers": {
97
+ "uxonfly": {
98
+ "command": "node",
99
+ "args": ["/absolute/path/to/uxonfly-mcp/build/index.js"]
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ ### After the first npm publish (coming soon)
106
+
107
+ Once `uxonfly-mcp` is on the npm registry, the install collapses to one command.
108
+
109
+ **Claude Code:**
110
+
111
+ ```bash
112
+ claude mcp add uxonfly -- npx -y uxonfly-mcp
113
+ ```
114
+
115
+ **Cursor** — `~/.cursor/mcp.json`:
116
+
117
+ ```json
118
+ {
119
+ "mcpServers": {
120
+ "uxonfly": {
121
+ "command": "npx",
122
+ "args": ["-y", "uxonfly-mcp"]
123
+ }
124
+ }
125
+ }
126
+ ```
127
+
128
+ ### Available tools
129
+
130
+ | Tool | When AI calls it | Returns |
131
+ | --- | --- | --- |
132
+ | `get_design_system` | Before any UI task | Full system |
133
+ | `get_component` | Before generating a component | Component spec |
134
+ | `get_tokens` | Before writing CSS / Tailwind | Tokens |
135
+ | `get_ux_pattern` | Before any UX decision | Pattern |
136
+ | `get_flow` | Before any multi-step flow | Flow spec |
137
+ | `get_copy_rules` | Before writing interface copy | Copy rules |
138
+ | `get_rules` | Before layout / IA decisions | Principles |
139
+
140
+ Each tool description begins with `ALWAYS call…` so your AI invokes them
141
+ proactively before generating UI code (the trick: the tool description
142
+ itself is the instruction).
143
+
144
+ ### Custom rulebook
145
+
146
+ Point the MCP server at your own version of `uxonfly.md` via the
147
+ `UXONFLY_MD_PATH` environment variable:
148
+
149
+ ```bash
150
+ # Local build (works today):
151
+ UXONFLY_MD_PATH=/absolute/path/to/your/uxonfly.md \
152
+ node /absolute/path/to/uxonfly-mcp/build/index.js
153
+
154
+ # After npm publish:
155
+ UXONFLY_MD_PATH=/absolute/path/to/your/uxonfly.md npx uxonfly-mcp
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Local development
161
+
162
+ ```bash
163
+ git clone https://github.com/Phanikondru/uxonfly-mcp.git
164
+ cd uxonfly-mcp
165
+ npm install
166
+ npm run dev # run with tsx
167
+ npm run build # compile to ./build
168
+ npm start # run the compiled server
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Contributing
174
+
175
+ The whole point of this project: one designer's opinions become a shared
176
+ standard the community keeps improving. PRs welcome.
177
+
178
+ How to help:
179
+
180
+ - Open an issue if you think a rule is wrong, missing, or unclear
181
+ - Send a PR for new components, patterns, or principles you've battle-tested
182
+ - Add real-world usage examples (apps you've built with `uxonfly.md`)
183
+
184
+ Goal: make UXonFly *better* than any one designer's taste alone.
185
+
186
+ ---
187
+
188
+ ## About
189
+
190
+ Built by [phanikondru](https://x.com/Phanikondru) — designer with 4 years
191
+ of production UX experience, building in public.
192
+
193
+ Just trying to build good things. **Design + AI + Code.**
194
+
195
+ UXonFly was built with Gemini, Cursor, and the rules in `uxonfly.md`
196
+ itself — the same system that ships with this package. The tool eats its
197
+ own dog food.
198
+
199
+ Follow the project: [@Phanikondru](https://x.com/Phanikondru)
200
+
201
+ ---
202
+
203
+ ## License
204
+
205
+ MIT. Use it commercially. Fork it. Modify it. Just keep the LICENSE file.
package/build/index.js ADDED
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * uxonfly-mcp — Designer intelligence for every AI coding session.
4
+ *
5
+ * A Model Context Protocol server exposing the UXonFly design system,
6
+ * UX patterns, and product principles to any MCP-compatible AI tool
7
+ * (Cursor, Claude Code, Windsurf, VS Code, Zed).
8
+ *
9
+ * Each tool description begins with "ALWAYS call..." so the AI invokes
10
+ * them proactively before writing UI code — the tool description is the
11
+ * instruction (see PRD §4.3).
12
+ */
13
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
+ import { z } from "zod";
16
+ import { readFileSync } from "node:fs";
17
+ import { fileURLToPath } from "node:url";
18
+ import { dirname, resolve } from "node:path";
19
+ // ─── Load the base system ────────────────────────────────────────────
20
+ const DEFAULT_MD_PATH = resolve(dirname(fileURLToPath(import.meta.url)), "..", "uxonfly.md");
21
+ function loadSystem() {
22
+ const path = process.env.UXONFLY_MD_PATH ?? DEFAULT_MD_PATH;
23
+ try {
24
+ return readFileSync(path, "utf8");
25
+ }
26
+ catch (err) {
27
+ console.error(`[uxonfly-mcp] failed to read ${path}:`, err);
28
+ process.exit(1);
29
+ }
30
+ }
31
+ const SYSTEM = loadSystem();
32
+ // ─── Markdown section slicing ────────────────────────────────────────
33
+ // Extracts content under a heading until the next heading of equal or
34
+ // shallower depth. Keeps the MCP server dependency-free (no markdown lib).
35
+ function getSection(md, heading, level = 2) {
36
+ const marker = "#".repeat(level) + " ";
37
+ const lines = md.split("\n");
38
+ let start = -1;
39
+ for (let i = 0; i < lines.length; i++) {
40
+ if (lines[i].trim() === (marker + heading).trim()) {
41
+ start = i + 1;
42
+ break;
43
+ }
44
+ }
45
+ if (start === -1)
46
+ return null;
47
+ let end = lines.length;
48
+ for (let i = start; i < lines.length; i++) {
49
+ const line = lines[i].trim();
50
+ if (line.startsWith("#")) {
51
+ const depth = line.match(/^#+/)?.[0].length ?? 0;
52
+ if (depth <= level) {
53
+ end = i;
54
+ break;
55
+ }
56
+ }
57
+ }
58
+ return lines.slice(start, end).join("\n").trim();
59
+ }
60
+ const asText = (text) => ({
61
+ content: [{ type: "text", text }],
62
+ });
63
+ const notFound = (what) => asText(`[uxonfly] not found: ${what}. Check uxonfly.md for available sections.`);
64
+ // ─── Server ──────────────────────────────────────────────────────────
65
+ const server = new McpServer({
66
+ name: "uxonfly-mcp",
67
+ version: "0.1.0",
68
+ });
69
+ // Tool 1 — complete design system
70
+ server.registerTool("get_design_system", {
71
+ title: "Get UXonFly Design System",
72
+ description: "ALWAYS call this tool before writing any UI code. Returns the complete 3-layer UXonFly design system for this project: visual language (tokens, components, layout), UX patterns (navigation, modals, forms, loading, empty states, errors, destructive actions, tables, notifications), and product principles. Do not invent design decisions — follow what this tool returns.",
73
+ inputSchema: {},
74
+ }, async () => asText(SYSTEM));
75
+ // Tool 2 — specific component
76
+ server.registerTool("get_component", {
77
+ title: "Get Component Spec",
78
+ description: "ALWAYS call this before generating any UI component (button, input, card, badge, modal, toast, etc.). Returns the full component spec: sizes, variants, states, and UX rules. Never improvise component specs.",
79
+ inputSchema: {
80
+ name: z
81
+ .string()
82
+ .describe("Component name, lowercase. E.g. 'button', 'input', 'card', 'badge', 'modal', 'toast'."),
83
+ },
84
+ }, async ({ name }) => {
85
+ const components = getSection(SYSTEM, "Components", 2);
86
+ if (!components)
87
+ return notFound("Components section");
88
+ const spec = getSection(components, name.toLowerCase(), 3);
89
+ return spec ? asText(spec) : notFound(`component '${name}'`);
90
+ });
91
+ // Tool 3 — design tokens
92
+ server.registerTool("get_tokens", {
93
+ title: "Get Design Tokens",
94
+ description: "ALWAYS call this before writing CSS, Tailwind config, CSS variables, or any styling code. Returns the complete token set: colors (with all semantic variants), typography scale, spacing scale, border radius, and shadows. Never use values outside these tokens.",
95
+ inputSchema: {},
96
+ }, async () => {
97
+ const tokens = getSection(SYSTEM, "Tokens", 2);
98
+ return tokens ? asText(tokens) : notFound("Tokens section");
99
+ });
100
+ // Tool 4 — UX pattern for a context
101
+ server.registerTool("get_ux_pattern", {
102
+ title: "Get UX Pattern",
103
+ description: "ALWAYS call this before making any UX decision. Covers destructive actions, forms, modals, loading states, empty states, errors, navigation, data tables, and notifications. Returns the correct pattern plus implementation guidance. Never guess at UX decisions.",
104
+ inputSchema: {
105
+ context: z
106
+ .string()
107
+ .describe("UX context. E.g. 'destructive-actions', 'forms', 'empty-states', 'errors', 'loading', 'modals', 'navigation', 'data-tables', 'notifications'."),
108
+ },
109
+ }, async ({ context }) => {
110
+ const patterns = getSection(SYSTEM, "UX Patterns", 2);
111
+ if (!patterns)
112
+ return notFound("UX Patterns section");
113
+ const match = getSection(patterns, context.toLowerCase(), 3);
114
+ return match ? asText(match) : notFound(`pattern '${context}'`);
115
+ });
116
+ // Tool 5 — multi-step flow
117
+ server.registerTool("get_flow", {
118
+ title: "Get UX Flow",
119
+ description: "ALWAYS call this before building any multi-step flow. Returns a complete flow spec: all screens, states, transitions, success and error handling. Never design flows from scratch.",
120
+ inputSchema: {
121
+ flow_name: z
122
+ .string()
123
+ .describe("Flow name. E.g. 'empty-state', 'onboarding', 'auth'."),
124
+ },
125
+ }, async ({ flow_name }) => {
126
+ const flows = getSection(SYSTEM, "Flows", 2);
127
+ if (!flows)
128
+ return notFound("Flows section");
129
+ const flow = getSection(flows, flow_name.toLowerCase(), 3);
130
+ return flow ? asText(flow) : notFound(`flow '${flow_name}'`);
131
+ });
132
+ // Tool 6 — copy rules
133
+ server.registerTool("get_copy_rules", {
134
+ title: "Get Copy Rules",
135
+ description: "ALWAYS call this before writing any interface copy — error messages, CTAs, empty-state text, tooltips, placeholders, labels. Returns tone, voice, tense, and format rules. Never write copy without checking these.",
136
+ inputSchema: {},
137
+ }, async () => {
138
+ const copy = getSection(SYSTEM, "Copy Rules", 2);
139
+ return copy ? asText(copy) : notFound("Copy Rules section");
140
+ });
141
+ // Tool 7 — product and design principles
142
+ server.registerTool("get_rules", {
143
+ title: "Get Product Principles",
144
+ description: "ALWAYS call this before making layout or information architecture decisions. Returns all product and design principles for this project: progressive disclosure, primary-action rules, error prevention, copy-driven UI, keyboard navigation, and accessibility.",
145
+ inputSchema: {},
146
+ }, async () => {
147
+ const rules = getSection(SYSTEM, "Principles", 2);
148
+ return rules ? asText(rules) : notFound("Principles section");
149
+ });
150
+ // ─── Start ───────────────────────────────────────────────────────────
151
+ async function main() {
152
+ const transport = new StdioServerTransport();
153
+ await server.connect(transport);
154
+ console.error("[uxonfly-mcp] running on stdio");
155
+ }
156
+ main().catch((err) => {
157
+ console.error("[uxonfly-mcp] fatal:", err);
158
+ process.exit(1);
159
+ });
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "uxonfly-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Designer intelligence for every AI coding session. Production-grade UX. On the fly.",
5
+ "type": "module",
6
+ "bin": {
7
+ "uxonfly-mcp": "build/index.js"
8
+ },
9
+ "files": [
10
+ "build",
11
+ "uxonfly.md",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
17
+ "dev": "tsx src/index.ts",
18
+ "start": "node build/index.js",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "design-system",
25
+ "ux",
26
+ "ai",
27
+ "cursor",
28
+ "claude-code",
29
+ "windsurf",
30
+ "vibe-coding"
31
+ ],
32
+ "author": {
33
+ "name": "phanikondru",
34
+ "url": "https://x.com/Phanikondru"
35
+ },
36
+ "license": "MIT",
37
+ "homepage": "https://uxonfly.dev",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/Phanikondru/uxonfly-mcp.git"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/Phanikondru/uxonfly-mcp/issues"
44
+ },
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.29.0",
47
+ "zod": "^3.23.8"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^22.19.2",
51
+ "tsx": "^4.19.2",
52
+ "typescript": "^5.9.3"
53
+ },
54
+ "engines": {
55
+ "node": ">=18"
56
+ }
57
+ }
package/uxonfly.md ADDED
@@ -0,0 +1,651 @@
1
+ # UXonFly — Design System
2
+
3
+ > Production-grade design and UX rules for AI coding sessions.
4
+ >
5
+ > Drop this file in your project root as `CLAUDE.md` (Claude Code) or
6
+ > `.cursorrules` (Cursor). Your AI will read it on every prompt and follow
7
+ > these rules instead of generating generic UI.
8
+ >
9
+ > Strong opinions. Border-first. Modern SaaS aesthetic
10
+ > (Linear / Stripe / Vercel-aligned). MIT licensed.
11
+ > Improvements welcome via PRs at github.com/Phanikondru/uxonfly-mcp.
12
+
13
+ ---
14
+
15
+ ## Tokens
16
+
17
+ ### Colors
18
+
19
+ Brand color: `#6366F1` (indigo). Replace with your own brand hex — every
20
+ other color is derived from it.
21
+
22
+ Semantic palette:
23
+
24
+ - `--color-primary`: #6366F1
25
+ - `--color-primary-hover`: #4F46E5
26
+ - `--color-primary-active`: #4338CA
27
+ - `--color-primary-subtle`: #EEF2FF
28
+ - `--color-text`: #0F172A (slate-900 — never pure black)
29
+ - `--color-text-muted`: #64748B (slate-500)
30
+ - `--color-text-subtle`: #94A3B8 (slate-400)
31
+ - `--color-surface`: #FFFFFF
32
+ - `--color-surface-subtle`: #F8FAFC (slate-50)
33
+ - `--color-surface-sunken`: #F1F5F9 (slate-100)
34
+ - `--color-border`: #E2E8F0 (slate-200)
35
+ - `--color-border-strong`: #CBD5E1 (slate-300)
36
+ - `--color-success`: #10B981 (emerald-500)
37
+ - `--color-warning`: #F59E0B (amber-500)
38
+ - `--color-danger`: #EF4444 (red-500)
39
+ - `--color-info`: #3B82F6 (blue-500)
40
+
41
+ Rules:
42
+
43
+ - Never use pure black (`#000`) for text. Use slate-900 (`#0F172A`).
44
+ - Never use pure white surfaces against muted backgrounds. Use slate-50 for muted areas.
45
+ - The primary brand color is for ONE primary action per screen. Don't decorate with it.
46
+ - Semantic colors (success / warning / danger) appear only on status indicators — never as decoration.
47
+
48
+ Dark mode: invert surface and text. Brand color stays the same hue.
49
+
50
+ - `--color-surface` → `#0F172A` (slate-900)
51
+ - `--color-surface-subtle` → `#1E293B` (slate-800)
52
+ - `--color-text` → `#F8FAFC` (slate-50)
53
+ - `--color-text-muted` → `#94A3B8` (slate-400)
54
+ - `--color-border` → `#1E293B` (slate-800)
55
+
56
+ ### Typography
57
+
58
+ - Display: **Cal Sans** (fallback: Inter Display)
59
+ - Body: **Inter**
60
+ - Mono: **JetBrains Mono**
61
+
62
+ Type scale (px):
63
+
64
+ - `xs`: 12 / 16 line-height
65
+ - `sm`: 14 / 20
66
+ - `base`: 16 / 24 (default body)
67
+ - `lg`: 18 / 28
68
+ - `xl`: 20 / 28
69
+ - `2xl`: 24 / 32
70
+ - `3xl`: 32 / 40
71
+ - `4xl`: 48 / 52
72
+
73
+ Weights:
74
+
75
+ - 400 (regular) — body text
76
+ - 500 (medium) — UI labels, buttons
77
+ - 600 (semibold) — headings, emphasis
78
+ - 700 (bold) — top-level page titles only, used sparingly
79
+
80
+ Rules:
81
+
82
+ - Body text is 16px minimum. Never smaller for readable content.
83
+ - Line height: body 1.5, headings 1.2.
84
+ - Never use more than 3 distinct font sizes on a single screen.
85
+
86
+ ### Spacing
87
+
88
+ 8-stop scale (pixels): `4 / 8 / 12 / 16 / 24 / 32 / 48 / 64`
89
+
90
+ Rules:
91
+
92
+ - Use only these values. No 7px, no 20px, no 50px.
93
+ - Default gap between major sections: 32px.
94
+ - Default gap between fields in a form: 16px.
95
+ - Default page padding: 24px desktop, 16px mobile.
96
+ - Compact UI (toolbars, dense tables): 8px gaps.
97
+
98
+ ### Shape
99
+
100
+ Border radius scale:
101
+
102
+ - `sm`: 4px (badges, small chips)
103
+ - `md`: 8px (default — buttons, inputs)
104
+ - `lg`: 12px (cards, modals)
105
+ - `xl`: 16px (hero sections, large containers)
106
+ - `full`: 9999px (avatars, pill buttons)
107
+
108
+ Rules:
109
+
110
+ - Default radius is `md` (8px). Use it unless there's a reason not to.
111
+ - Never mix radius sizes inside the same component.
112
+ - Never use square corners (radius 0) on interactive elements.
113
+
114
+ ### Shadows
115
+
116
+ **Border-first philosophy**: separation comes from borders, not shadows.
117
+
118
+ One shadow only — for genuinely elevated things:
119
+
120
+ ```
121
+ shadow-elevated: 0 1px 3px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.06)
122
+ ```
123
+
124
+ Use for:
125
+
126
+ - Modals (backdrop creates the elevation, shadow makes it feel held)
127
+ - Popovers and dropdowns
128
+ - Toasts
129
+ - Floating UI (FABs, command palettes)
130
+
131
+ Never use for:
132
+
133
+ - Cards in a list (use a border instead)
134
+ - Buttons (use the primary color, not a shadow, to indicate elevation)
135
+ - Inputs (use a focus ring, not a shadow)
136
+ - Headers, sidebars, navigation (use a `border-bottom`)
137
+
138
+ If you find yourself reaching for a shadow on a card, you want a border.
139
+
140
+ ---
141
+
142
+ ## Components
143
+
144
+ ### button
145
+
146
+ Sizes:
147
+
148
+ - `sm`: 32px height, 12px horizontal padding, 14px text
149
+ - `md`: 40px height, 16px horizontal padding, 14px text (default)
150
+ - `lg`: 48px height, 24px horizontal padding, 16px text
151
+
152
+ Variants:
153
+
154
+ - `primary` — brand color background, white text. ONE per screen, max.
155
+ - `secondary` — surface background, border, text color. Default for everything that isn't the primary action.
156
+ - `ghost` — transparent background, no border, text color. For tertiary actions in toolbars and inline contexts.
157
+ - `danger` — red background, white text. For destructive actions only. Always paired with a 2-step confirmation.
158
+
159
+ States (every variant):
160
+
161
+ - `default` — base styling
162
+ - `hover` — slightly darker background (use the `-hover` token)
163
+ - `active` — even darker background (use the `-active` token)
164
+ - `focus-visible` — 2px ring in primary color at 50% opacity, 2px offset
165
+ - `disabled` — 50% opacity, `cursor: not-allowed`, no hover
166
+ - `loading` — spinner replaces label, button width stays the same
167
+
168
+ Rules:
169
+
170
+ - Maximum ONE `primary` button per screen. If two actions are equally important, redesign the screen.
171
+ - Destructive actions ALWAYS use the `danger` variant. Never red-color a primary button.
172
+ - Loading state: spinner replaces the label text but the button width does not change.
173
+ - Never use buttons for navigation. Navigation is for `<a>` tags. Buttons are for actions.
174
+ - Icon-only buttons require an `aria-label`.
175
+
176
+ ### input
177
+
178
+ Default size: 40px height, 12px horizontal padding, 14px text.
179
+ Border: 1px solid `--color-border`, radius `md`.
180
+ Focus: 2px ring in `--color-primary` at 50% opacity, 2px offset.
181
+
182
+ Rules:
183
+
184
+ - Label ALWAYS above the field, always visible. Never use placeholder-as-label.
185
+ - Mark optional fields with `(optional)`. Never mark required fields with `*`.
186
+ - Validation runs on `blur`, never on every keystroke (one exception: password strength meters).
187
+ - Error messages appear below the field, in `--color-danger`, prefixed with an icon.
188
+ - Placeholders are example values (`jane@example.com`), never instructions ("Enter your email").
189
+ - Help text and error text share the same vertical slot — no layout shift between states.
190
+
191
+ ### card
192
+
193
+ Background: `--color-surface`.
194
+ Border: 1px solid `--color-border` (NOT shadow).
195
+ Radius: `lg` (12px).
196
+ Padding: 24px.
197
+
198
+ Rules:
199
+
200
+ - Cards group related content. Don't use a card for a single piece of content.
201
+ - Cards in a list are separated by 16px vertical gaps. No additional shadows or dividers.
202
+ - Hoverable cards get `--color-border-strong` on hover, no shadow.
203
+ - Never nest cards more than one level deep.
204
+
205
+ ### badge
206
+
207
+ Height: 24px.
208
+ Padding: 0 8px.
209
+ Radius: `full`.
210
+ Text: 12px, weight 500.
211
+
212
+ Variants:
213
+
214
+ - `neutral` — slate-100 background, slate-700 text
215
+ - `primary` — primary-subtle background, primary text
216
+ - `success` — emerald-50 background, emerald-700 text
217
+ - `warning` — amber-50 background, amber-700 text
218
+ - `danger` — red-50 background, red-700 text
219
+ - `info` — blue-50 background, blue-700 text
220
+
221
+ Rules:
222
+
223
+ - Badges are for status, counts, or labels. Never for navigation, never for actions.
224
+ - Never use a badge as a button. If it's clickable, it's a button.
225
+ - Never put more than one badge on a single piece of content unless they encode different information types (status + count is fine; two statuses is not).
226
+
227
+ ### modal
228
+
229
+ Default max-width: 480px.
230
+ Wide variant: 640px.
231
+ Full variant: 90vw (only for large media or complex flows).
232
+ Backdrop: `rgba(15, 23, 42, 0.5)`.
233
+ Padding: 24px.
234
+ Radius: `lg`.
235
+ Shadow: `shadow-elevated`.
236
+
237
+ Required close affordances (ALL THREE):
238
+
239
+ - Close button (X) in the top-right
240
+ - Click on backdrop dismisses
241
+ - `Esc` key dismisses
242
+
243
+ Rules:
244
+
245
+ - Modals interrupt the user — only use them for short, focused tasks.
246
+ - NEVER nest modals inside modals. Redesign the flow.
247
+ - NEVER require horizontal scroll inside a modal.
248
+ - Modal title is required. Plain language, sentence case.
249
+ - Primary action is bottom-right. Cancel is bottom-left or the top-right X.
250
+ - Destructive modals follow the pattern in `### destructive-actions`.
251
+
252
+ ### toast
253
+
254
+ Position: bottom-right on desktop, top-center on mobile.
255
+ Width: 360px max.
256
+ Padding: 16px.
257
+ Radius: `md`.
258
+ Shadow: `shadow-elevated`.
259
+ Stack: max 3 visible at once. Older toasts dismiss when the 4th appears.
260
+
261
+ Auto-dismiss timing:
262
+
263
+ - Success: 4 seconds
264
+ - Info: 5 seconds
265
+ - Warning: 7 seconds
266
+ - Error: 10 seconds
267
+ - With required action (e.g. "Undo"): never auto-dismiss until clicked
268
+
269
+ Rules:
270
+
271
+ - Toasts are for confirmations and non-critical errors. Never for blocking information.
272
+ - Toasts always include an icon matching the variant.
273
+ - Toasts NEVER contain "view more" links — the message must stand alone.
274
+ - Errors that need user action go inline or as a banner — NOT as a toast.
275
+
276
+ ---
277
+
278
+ ## UX Patterns
279
+
280
+ ### navigation
281
+
282
+ - 5+ top-level sections → sidebar (left, fixed, 240px wide)
283
+ - 2–4 views → tabs (top of content, underline style)
284
+ - Hierarchy beyond 2 levels deep → breadcrumbs (above the page title)
285
+
286
+ Rules:
287
+
288
+ - Hamburger menus are FORBIDDEN on screens ≥1024px wide.
289
+ - Sidebar nav items are vertical icons + text. Never icon-only on desktop.
290
+ - Active state: brand color text + brand color subtle background. NOT a colored bar.
291
+ - Tabs: underline only, no background fill. Active tab gets brand color underline + text.
292
+ - Breadcrumbs: separator is `/`, never `>`. Last item is the current page (no link).
293
+
294
+ ### modals
295
+
296
+ Use a modal when:
297
+
298
+ - The task is short (under 30 seconds)
299
+ - The task is focused (single clear outcome)
300
+ - The task interrupts the current flow intentionally (confirmation, quick edit)
301
+
302
+ Use a new page when:
303
+
304
+ - The task has multiple steps
305
+ - The task requires reference to other content on the page
306
+ - The task IS the primary flow (creating the main resource of the app)
307
+
308
+ Forbidden:
309
+
310
+ - Modal nested inside a modal
311
+ - Modal that opens another modal on success
312
+ - Modal taller than the viewport with internal scroll AND a sticky footer
313
+ - Modal without a close X button
314
+
315
+ ### forms
316
+
317
+ Layout:
318
+
319
+ - Single column by default, ALWAYS. Two-column only when fields are paired (first/last name, city/zip).
320
+ - Field gap: 16px vertical.
321
+ - Section gap (within a form): 32px.
322
+
323
+ Labels:
324
+
325
+ - Above the field, always visible. Sentence case. Singular ("Project name", not "Project Names:").
326
+ - No colon at the end.
327
+
328
+ Optional vs required:
329
+
330
+ - Mark OPTIONAL fields with `(optional)` after the label.
331
+ - Never mark required fields with `*` or "required".
332
+
333
+ Validation:
334
+
335
+ - On `blur`, NEVER on keystroke (one exception: password strength meters).
336
+ - On submit if the user has not yet blurred the field.
337
+ - Error message below the field, in danger color, prefixed with icon.
338
+
339
+ Submit button:
340
+
341
+ - Bottom-right by default.
342
+ - Cancel button to its left, secondary or ghost variant.
343
+ - Submit label is always verb + object: "Create project", not "Submit" or "OK".
344
+
345
+ Forbidden:
346
+
347
+ - "Reset" buttons. Use Cancel instead.
348
+ - Submit disabled until the form is valid (the user can't tell what's missing).
349
+ - Multiple submit buttons.
350
+
351
+ ### loading
352
+
353
+ Apply by perceived task duration:
354
+
355
+ | Estimated time | Treatment |
356
+ | --- | --- |
357
+ | < 100ms | Nothing. Feels instant. |
358
+ | 100ms – 1s | Nothing visible, OR a brief shimmer if the user might wonder. |
359
+ | 1s – 3s | Inline spinner OR skeleton if the shape is known. |
360
+ | 3s+ | Progress bar + descriptive text ("Importing 1,200 rows…") |
361
+ | Unknown duration | Skeleton if shape known, indeterminate spinner with text otherwise. |
362
+
363
+ Rules:
364
+
365
+ - Skeleton vs spinner: skeleton if you know the shape (a card grid, a table, a profile). Spinner if you don't.
366
+ - NEVER show a spinner AND a skeleton on the same content at the same time.
367
+ - Loading replaces the content. Don't overlay a spinner on stale content unless the action is a refresh.
368
+ - Loading on a button: spinner replaces the label inline; button width stays the same.
369
+
370
+ ### empty-states
371
+
372
+ ALWAYS four elements, in this order:
373
+
374
+ 1. **Illustration** — small (max 120px tall), monochrome or low-contrast, never a photo.
375
+ 2. **Heading** — what the user is seeing, in 1 short sentence. "No projects yet."
376
+ 3. **Subtext** — what they can do about it, in 1 sentence. "Create your first project to start tracking work."
377
+ 4. **Primary CTA button** — verb + object. "Create project."
378
+
379
+ Forbidden:
380
+
381
+ - Empty area with no content
382
+ - Generic "No data" string
383
+ - Disabled CTA
384
+ - More than one CTA (no "Learn more" + "Create" — pick one)
385
+ - Apologetic tone ("Sorry, no items found")
386
+
387
+ ### errors
388
+
389
+ Choose by error type:
390
+
391
+ | Error | Treatment |
392
+ | --- | --- |
393
+ | Form validation | Inline, below the field |
394
+ | Action failure (e.g. save failed) | Inline at the action point, OR top-of-form banner |
395
+ | Page-level (5xx, network) | Top-of-page banner with retry button |
396
+ | Critical, blocks all action | Full-page error state with retry CTA |
397
+
398
+ Forbidden:
399
+
400
+ - Errors as toasts (toasts auto-dismiss; the user might miss critical info)
401
+ - Raw error codes ("Error 500") — translate to plain language
402
+ - Error messages without next steps ("Something went wrong" — say WHAT and what to do)
403
+
404
+ Plain language rule:
405
+
406
+ - Always tell the user (a) what failed, (b) what to do next.
407
+ - Bad: `Error: ENOENT no such file or directory`
408
+ - Good: `Couldn't find the file. It may have been moved. Refresh the page or upload it again.`
409
+
410
+ ### destructive-actions
411
+
412
+ Minimum 2 steps: trigger → confirmation.
413
+
414
+ Confirmation modal copy format:
415
+
416
+ ```
417
+ Title: Delete [resource type]?
418
+ Body: This will permanently delete [resource name]. This cannot be undone.
419
+ Cancel: Cancel
420
+ Danger: Delete [resource type]
421
+ ```
422
+
423
+ For HIGH-VALUE destructive actions (deleting a workspace, an account, paid data):
424
+
425
+ - Require typed confirmation: "Type [resource name] to confirm"
426
+ - Disable the danger button until the typed input matches exactly
427
+
428
+ After success:
429
+
430
+ - If reversible within a window: show an undo toast for 10 seconds.
431
+ - If irreversible: show a success toast confirming what was deleted, no undo.
432
+
433
+ Forbidden:
434
+
435
+ - Single-click delete with no confirmation
436
+ - "Are you sure?" with an OK button (vague + non-destructive button color)
437
+ - Confirmations where the danger and cancel buttons look similar
438
+
439
+ ### data-tables
440
+
441
+ Pagination:
442
+
443
+ - Show pagination at 25+ rows.
444
+ - Default page size: 25. Allowed: 25 / 50 / 100.
445
+ - Page navigation: bottom-right. Total count: bottom-left.
446
+
447
+ Sorting:
448
+
449
+ - Click the column header to sort. Click again to reverse. Click a third time to clear.
450
+ - Sort indicator: arrow up/down icon next to the column label.
451
+
452
+ Filtering:
453
+
454
+ - Filters live ABOVE the table, left-aligned.
455
+ - Filter chips show active filters; X to remove.
456
+ - "Clear all filters" appears when 2+ filters are active.
457
+
458
+ Bulk actions:
459
+
460
+ - Row selection via leading checkbox.
461
+ - Bulk action bar appears above the table when 1+ rows selected.
462
+ - Bulk action bar shows: count selected, available actions, "deselect all".
463
+
464
+ Column behavior:
465
+
466
+ - Sticky first column when horizontal scroll is needed.
467
+ - Truncate long values with `…` and a tooltip showing the full value on hover.
468
+ - Right-align numbers, left-align text and dates.
469
+
470
+ Forbidden:
471
+
472
+ - Horizontal scroll without sticky first column
473
+ - Pagination AND infinite scroll on the same table
474
+ - Sortable columns without a visible sort indicator
475
+
476
+ ### notifications
477
+
478
+ Three types — never use the wrong one.
479
+
480
+ | Type | Use for | Position | Persistence |
481
+ | --- | --- | --- | --- |
482
+ | **Toast** | Transient confirmation (saved, sent, copied) | Bottom-right | Auto-dismiss |
483
+ | **Banner** | Persistent state info (trial ending, system maintenance) | Top of page | Dismissible by user |
484
+ | **Inline** | Field-level feedback (validation error, status near a control) | Adjacent to the control | Persists until resolved |
485
+
486
+ Rules:
487
+
488
+ - Critical errors NEVER use toasts. Use a banner or inline error.
489
+ - Banners stack at the top in priority order — most critical first.
490
+ - Inline notifications never auto-dismiss.
491
+
492
+ ---
493
+
494
+ ## Flows
495
+
496
+ ### empty-state
497
+
498
+ Steps:
499
+
500
+ 1. Route loads. Detect empty data.
501
+ 2. Render the empty state component (illustration + heading + subtext + CTA — see UX Patterns → empty-states).
502
+ 3. CTA leads DIRECTLY to the create flow. No intermediate "welcome" screen.
503
+ 4. After successful creation, route back to the populated view with a success toast: "[Resource] created."
504
+
505
+ Anti-patterns:
506
+
507
+ - Empty state that just says "Loading…" for a second before content appears (use a skeleton instead)
508
+ - Empty state with multiple CTAs ("Create" AND "Learn more")
509
+ - Empty state that disables the CTA until the user completes onboarding
510
+
511
+ ### onboarding
512
+
513
+ Philosophy: minimum viable onboarding. Skip is ALWAYS allowed.
514
+
515
+ Steps:
516
+
517
+ 1. **Welcome screen** — one sentence about what the product does. One primary CTA: "Get started." One secondary: "Skip."
518
+ 2. **Minimum collection** — name + primary use case. NO MORE THAN 2 INPUTS. If you need 5 fields, your onboarding is wrong.
519
+ 3. **One contextual highlight** — when the user lands on the main view, point at the most important thing with a small popover. Just one. Dismissible.
520
+ 4. **Mark as onboarded** — never show the onboarding again, even if the user clears state.
521
+
522
+ Forbidden:
523
+
524
+ - Multi-step wizards with 5+ screens
525
+ - Required onboarding (no skip allowed)
526
+ - Tutorial overlays that grey out the screen and block interaction
527
+ - Asking for payment, phone number, or company size in onboarding
528
+
529
+ ### auth
530
+
531
+ Steps:
532
+
533
+ 1. **Single screen, sign-in OR sign-up** — one email field. The button auto-detects: "Sign in" if account exists, "Create account" if not.
534
+ 2. **Authentication method** — magic link OR password OR OAuth. NEVER show all three side by side. Pick one primary, others as smaller text-link alternatives.
535
+ 3. **After auth** — route to the user's last intended destination. Default: dashboard.
536
+ 4. **Errors are inline** — never in a toast. "Wrong password" appears below the password field.
537
+
538
+ Forbidden:
539
+
540
+ - Separate "Sign in" and "Sign up" pages with identical fields
541
+ - Forced password complexity requirements visible before the user has typed anything
542
+ - "Remember me" checkboxes (assume yes — provide a sign-out instead)
543
+ - CAPTCHA on the first login attempt
544
+
545
+ ---
546
+
547
+ ## Copy Rules
548
+
549
+ **Voice**: direct, human, never cute. Closest reference: Linear, Stripe, Vercel.
550
+ **Tense**: present.
551
+ **Person**: second person (you / your).
552
+
553
+ ### Errors
554
+
555
+ - Tell the user (a) what failed, (b) what to do next.
556
+ - Never show error codes ("Error 500", "ENOENT", etc.). Translate.
557
+ - Bad: `Failed to fetch.`
558
+ - Good: `Couldn't load your projects. Check your connection and try again.`
559
+
560
+ ### CTAs
561
+
562
+ - Format: verb + object. "Create project", "Send invite", "Delete account."
563
+ - Forbidden: "Submit", "OK", "Click here", "Continue" (when the action is destructive or specific).
564
+
565
+ ### Empty states
566
+
567
+ - Encouraging, not apologetic.
568
+ - Bad: `Sorry, no projects found.`
569
+ - Good: `No projects yet. Create your first to get started.`
570
+
571
+ ### Tooltips
572
+
573
+ - One sentence max.
574
+ - No trailing punctuation.
575
+ - Bad: `This will permanently delete the selected items. This action cannot be undone.`
576
+ - Good: `Delete the selected items`
577
+
578
+ ### Placeholders
579
+
580
+ - Example values, never instructions.
581
+ - Bad: `Enter your email`
582
+ - Good: `jane@example.com`
583
+
584
+ ### Labels
585
+
586
+ - Sentence case, singular.
587
+ - No colons at the end.
588
+ - Bad: `PROJECT NAMES:`
589
+ - Good: `Project name`
590
+
591
+ ### Headings
592
+
593
+ - Title case for H1 page titles. Sentence case for H2/H3 section headings.
594
+ - Maximum 5 words for H1.
595
+
596
+ ### Numbers
597
+
598
+ - Spell out one through nine in body copy. Use numerals for 10+.
599
+ - Always use numerals in UI labels, table cells, and stats.
600
+
601
+ ---
602
+
603
+ ## Principles
604
+
605
+ These are the north stars. When two rules conflict, the one closer to the top wins.
606
+
607
+ ### 1. Accessibility is not a feature
608
+
609
+ WCAG AA minimum, no exceptions. Contrast enforced at the token level. ARIA
610
+ landmarks on every route. Keyboard reachable for every interactive element.
611
+ Focus-visible rings on everything that can be tabbed to. If your design fails
612
+ accessibility, it fails — period.
613
+
614
+ ### 2. One primary action per screen
615
+
616
+ If the user lands on a screen and there are two equally important things to
617
+ do, redesign the screen. Secondary actions are visually subordinate. No
618
+ competing CTAs. The user should never have to ask "what's the main thing here?"
619
+
620
+ ### 3. Error prevention over recovery
621
+
622
+ Design so mistakes are hard to make in the first place. Default to safe
623
+ inputs. Make destructive actions require deliberate effort (typed confirmation
624
+ for high-value cases). Surface validation early enough that the user can fix
625
+ it before they commit.
626
+
627
+ ### 4. Progressive disclosure
628
+
629
+ Show what the user needs RIGHT NOW. Reveal more on demand. The default view
630
+ is the most common case. Power features live one click deeper. A user should
631
+ be able to ignore 80% of the interface 80% of the time.
632
+
633
+ ### 5. Copy drives decisions
634
+
635
+ Write the copy before designing the UI. If the copy is vague, the UI is wrong.
636
+ If the heading takes 3 seconds to understand, no font choice will save it.
637
+ Test by reading aloud — if it sounds like a robot, rewrite it.
638
+
639
+ ### 6. Keyboard navigable by default
640
+
641
+ Every interactive element is reachable via Tab in logical order. Every
642
+ interactive element shows a visible focus ring when focused. Esc dismisses
643
+ overlays (modals, popovers, dropdowns). Enter submits the focused form.
644
+ Arrow keys navigate within components (tabs, menus, listboxes).
645
+
646
+ ### 7. Trust the system
647
+
648
+ Don't second-guess these rules to "make it pop" or "match the brand." The
649
+ brand is your color and your typography. Everything else is consistent across
650
+ all UXonFly-built apps so your AI doesn't drift between sessions. If a rule
651
+ is wrong, fix it in this file — don't override it case by case.