@nqlib/nqui 0.5.6 → 0.6.1
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/dist/{command-palette-DhoWGyk_.js → command-palette-CHUiGh5m.js} +2 -2
- package/dist/{command-palette-D24rOeE6.cjs → command-palette-DsvP2QNP.cjs} +2 -2
- package/dist/command.cjs.js +1 -1
- package/dist/command.es.js +1 -1
- package/dist/components/custom/table-of-contents.d.ts +2 -2
- package/dist/components/custom/table-of-contents.d.ts.map +1 -1
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/theme-appearance-menu.d.ts +9 -0
- package/dist/components/theme-appearance-menu.d.ts.map +1 -0
- package/dist/components/theme-toggle.d.ts +2 -0
- package/dist/components/theme-toggle.d.ts.map +1 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -1
- package/dist/components/ui/combobox.d.ts +5 -3
- package/dist/components/ui/combobox.d.ts.map +1 -1
- package/dist/components/ui/toggle-group.d.ts.map +1 -1
- package/dist/{debug-panel-BjfW-YVo.js → debug-panel-BcYzsTp2.js} +1 -1
- package/dist/{debug-panel-CpqsKuxd.cjs → debug-panel-mwtujy5J.cjs} +1 -1
- package/dist/debug.cjs.js +1 -1
- package/dist/debug.es.js +1 -1
- package/dist/elevation-debate.html +286 -0
- package/dist/{input-shared-CDgy_NdJ.cjs → input-shared-C9Try5fg.cjs} +1 -1
- package/dist/{input-shared-NnOiyHpu.js → input-shared-DXf3Edqt.js} +1 -1
- package/dist/lib/floating-surface.d.ts +6 -2
- package/dist/lib/floating-surface.d.ts.map +1 -1
- package/dist/nqui.cjs.js +15 -13
- package/dist/nqui.es.js +2752 -2646
- package/dist/styles.css +151 -255
- package/docs/components/README.md +4 -1
- package/docs/components/nqui-combobox.md +15 -2
- package/docs/components/nqui-command-palette.md +7 -0
- package/docs/components/nqui-command.md +41 -0
- package/docs/components/nqui-scroll-area.md +69 -0
- package/docs/nqui-skills/AGENT_PROMPT.md +190 -0
- package/docs/nqui-skills/COMPONENTS_INDEX.md +51 -1
- package/docs/nqui-skills/COMPOSITION.md +321 -0
- package/docs/nqui-skills/ELEVATION.md +181 -0
- package/docs/nqui-skills/EVAL.md +148 -0
- package/docs/nqui-skills/HUMAN_GUIDE.md +18 -0
- package/docs/nqui-skills/MIGRATION.md +133 -0
- package/docs/nqui-skills/MOTION.md +189 -0
- package/docs/nqui-skills/README.md +2 -0
- package/docs/nqui-skills/READ_BUDGET.md +60 -0
- package/docs/nqui-skills/RECIPES.md +820 -0
- package/docs/nqui-skills/SKILL.md +58 -1
- package/docs/nqui-skills/STATES.md +154 -0
- package/docs/nqui-skills/THEMING.md +203 -0
- package/docs/nqui-skills/WRITING.md +205 -0
- package/docs/nqui-skills/_claude-commands/README.md +50 -0
- package/docs/nqui-skills/_claude-commands/design/SKILL.md +111 -0
- package/docs/nqui-skills/_claude-commands/edit/SKILL.md +97 -0
- package/docs/nqui-skills/adapt/SKILL.md +5 -2
- package/docs/nqui-skills/animate/SKILL.md +5 -2
- package/docs/nqui-skills/audit/SKILL.md +5 -2
- package/docs/nqui-skills/bolder/SKILL.md +5 -2
- package/docs/nqui-skills/clarify/SKILL.md +5 -2
- package/docs/nqui-skills/colorize/SKILL.md +5 -2
- package/docs/nqui-skills/delight/SKILL.md +5 -4
- package/docs/nqui-skills/distill/SKILL.md +5 -2
- package/docs/nqui-skills/impeccable/SKILL.md +0 -16
- package/docs/nqui-skills/impeccable/reference/INDEX.md +26 -0
- package/docs/nqui-skills/layout/SKILL.md +5 -2
- package/docs/nqui-skills/nqui-components/SKILL.md +33 -9
- package/docs/nqui-skills/nqui-composition/SKILL.md +148 -0
- package/docs/nqui-skills/nqui-data-tables/SKILL.md +127 -0
- package/docs/nqui-skills/nqui-design-system/SKILL.md +22 -1
- package/docs/nqui-skills/nqui-install/SKILL.md +1 -0
- package/docs/nqui-skills/overdrive/SKILL.md +5 -2
- package/docs/nqui-skills/polish/SKILL.md +5 -4
- package/docs/nqui-skills/quieter/SKILL.md +5 -2
- package/docs/nqui-skills/shape/SKILL.md +5 -2
- package/docs/nqui-skills/typeset/SKILL.md +5 -2
- package/package.json +2 -1
- package/scripts/build-styles.js +4 -3
- package/scripts/cli.js +2 -0
- package/scripts/install-claude-skills.js +148 -0
|
@@ -9,17 +9,74 @@ SSOT: `packages/nqui/docs/nqui-skills/`
|
|
|
9
9
|
|
|
10
10
|
Installed into `.cursor/nqui-skills/` via `npx @nqlib/nqui init-skills`.
|
|
11
11
|
|
|
12
|
+
> **Token discipline:** Before loading multiple files, read **[READ_BUDGET.md](./READ_BUDGET.md)** — it routes any task to 1–3 files. The skills folder is ~5000 lines; don't bulk-read.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Agent Build Protocol
|
|
17
|
+
|
|
18
|
+
When asked to **build a new view, page, or feature**, follow this order:
|
|
19
|
+
|
|
20
|
+
1. **Load design context** — Check `.impeccable.md` in project root. If missing, run `/impeccable teach` before any design work.
|
|
21
|
+
2. **Understand the user's job** — One outcome, one primary action. What moves to a secondary surface? (See `nqui-composition/SKILL.md` checklist.)
|
|
22
|
+
3. **Pick a layout pattern** — Read `COMPOSITION.md` (named patterns: app shell, settings, dashboard, wizard, master-detail). Match the pattern to the job.
|
|
23
|
+
4. **Look up combos** — Read `RECIPES.md` for the specific situations you need (status pill, bulk actions, filter toolbar, the three states, etc.). Don't reinvent.
|
|
24
|
+
5. **Select components** — Use `HUMAN_GUIDE.md` (task → doc) and `COMPONENTS_INDEX.md` decision tables. Load one `nqui-<name>.md` per component.
|
|
25
|
+
6. **Apply the design system** — Load `nqui-design-system/SKILL.md` for sizing, spacing, and scroll contracts.
|
|
26
|
+
7. **Cover all states** — Walk `STATES.md` for every interactive surface. Empty, loading, error are mandatory.
|
|
27
|
+
8. **Write the copy properly** — Apply `WRITING.md` rules to every button, label, error, empty state, and toast. No "Submit", no "An error occurred", no exclamation marks.
|
|
28
|
+
9. **Add motion only with intent** — If you animate anything, consult `MOTION.md` for timing/easing/choreography. Default to no motion unless it serves comprehension.
|
|
29
|
+
10. **Build, then verify** — Run the pre-ship checklist in `COMPOSITION.md`. Run `/audit` on complex views.
|
|
30
|
+
|
|
31
|
+
**Never skip step 1.** Generic output comes from missing design context, not missing components.
|
|
32
|
+
**Never skip step 4** for product UI. The recipes encode lessons learned — bypassing them produces busy, AI-flavored work.
|
|
33
|
+
**Never skip steps 7-9.** Missing states, lazy copy, and noisy motion are the three things that make AI-built UI feel AI-built.
|
|
34
|
+
|
|
35
|
+
## The 30-second Linear designer test (mandatory before declaring done)
|
|
36
|
+
|
|
37
|
+
Before saying "this is complete," do this thought experiment:
|
|
38
|
+
|
|
39
|
+
> *If I showed this view to a senior designer at Linear, Vercel, or Things 3 — someone who builds product UI all day — what would they change in the first 30 seconds?*
|
|
40
|
+
|
|
41
|
+
The honest answer is almost always one or more of these:
|
|
42
|
+
|
|
43
|
+
1. **Hierarchy is weak** — too many same-weight elements competing
|
|
44
|
+
2. **Empty/loading/error states are missing** or generic
|
|
45
|
+
3. **Copy is lazy** — "Submit", "An error occurred", "Are you sure?"
|
|
46
|
+
4. **Toolbar floats** instead of sitting in a `bg-muted/30` container
|
|
47
|
+
5. **Multiple primary buttons** competing for attention
|
|
48
|
+
6. **Spacing is monotonous** — same padding everywhere, no rhythm
|
|
49
|
+
7. **Cards wrap everything** including things that don't need a card
|
|
50
|
+
8. **No keyboard navigation** — buttons but no focus styles, no shortcuts
|
|
51
|
+
9. **Decorative shadows / gradients** on flat elements that don't need depth
|
|
52
|
+
10. **Motion is missing or busy** — either no transitions at all, or too many
|
|
53
|
+
|
|
54
|
+
If you can name 2+ items from that list, the view isn't done. Fix them BEFORE asking the user. The point is to ship work that doesn't trigger any of these — not to ask the user "is this OK?" when you already know it isn't.
|
|
55
|
+
|
|
12
56
|
---
|
|
13
57
|
|
|
14
58
|
## nqui Component Library
|
|
15
59
|
|
|
16
60
|
| Skill | When to use |
|
|
17
61
|
| ------------------------------- | ------------------------------------------------------------------ |
|
|
62
|
+
| **nqui-composition** | Apple-inspired craft for composing screens (clarity / deference / depth). Read before building new views. |
|
|
63
|
+
| **COMPOSITION.md** | Named layout patterns, three-surface cap, anti-patterns, pre-ship checklist — read before building any view |
|
|
64
|
+
| **RECIPES.md** | Component combos for common product situations (status pill, bulk actions, the three states, etc.) |
|
|
65
|
+
| **STATES.md** | The 12-state matrix every interactive surface must cover. Mandatory checklist per view. |
|
|
66
|
+
| **WRITING.md** | UX copy rules — buttons, errors, empty states, microcopy. The differentiator between AI-built and human-built. |
|
|
67
|
+
| **MOTION.md** | Motion as a system — timing scale, easing vocabulary, choreography, reduced-motion. Read before animating anything. |
|
|
68
|
+
| **ELEVATION.md** | The 2+1 surface hybrid model — cap inline nesting at 2 surfaces, use spacing for the rest. Read before any layered UI. |
|
|
69
|
+
| **HUMAN_GUIDE.md** | Task → component doc routing (forms, toolbar, dashboard, overlays, states) |
|
|
70
|
+
| **AGENT_PROMPT.md** | Canonical system prompt for AI agents using nqui. Reference this in your agent setup. |
|
|
71
|
+
| **EVAL.md** | 12-prompt eval set + grading rubric. Run before releases. |
|
|
72
|
+
| **THEMING.md** | Brand customization without breaking the rules. |
|
|
73
|
+
| **MIGRATION.md** | Breaking changes per release. Scan before upgrading. |
|
|
18
74
|
| **nqui-components** | Implementing UI, choosing components, component props and examples |
|
|
19
|
-
| **nqui-design-system** | Design token conventions, spacing, Card + ScrollArea
|
|
75
|
+
| **nqui-design-system** | Design token conventions, spacing, Card + ScrollArea / bounded panels (see `nqui-scroll-area.md` §0) |
|
|
20
76
|
| **nqui-shadcn** | Managing nqui components (adding, fixing, styling, composing UI) |
|
|
21
77
|
| **nqui-install** | Install, setup peers, CSS init, CLI commands |
|
|
22
78
|
| **nqui-bundle-size** | Bundle size best practices when adding deps or creating packages |
|
|
79
|
+
| **nqui-data-tables** | TanStack/native HTML tables + ScrollArea: bounded height, sticky header, HV scroll, IO or paging |
|
|
23
80
|
| **nqui-local-published-toggle** | Switching between local and published @nqlib/nqui |
|
|
24
81
|
|
|
25
82
|
---
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nqui-states
|
|
3
|
+
description: The complete state matrix every interactive thing must handle. Use when building any view or component to ensure you've designed for ALL states, not just the happy path. AI work overwhelmingly fails on missing states (empty, loading, error, edge). This file is the checklist.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# nqui States — The Complete Matrix
|
|
7
|
+
|
|
8
|
+
The #1 reason AI-built UIs feel unfinished: they design the happy path and forget the other 11 states. This file is the checklist. Read it for every screen and component. Don't ship until every applicable state has an intentional design.
|
|
9
|
+
|
|
10
|
+
## The 12 states every interactive surface can be in
|
|
11
|
+
|
|
12
|
+
| State | When | Designed how |
|
|
13
|
+
|-------|------|--------------|
|
|
14
|
+
| **Idle** | Default resting state | The thing you usually think of as "the design" |
|
|
15
|
+
| **Hover** | Pointer over (desktop only — degrade gracefully on touch) | Subtle background shift, never a transform |
|
|
16
|
+
| **Focus** | Keyboard navigation lands here | Visible ring (`focus-visible:ring-2 ring-ring`), never `outline:none` alone |
|
|
17
|
+
| **Active / Pressed** | Mouse down / tap in progress | Slight darken or inset shadow, fast (<100ms) |
|
|
18
|
+
| **Selected** | User has chosen this | Persistent visual change (e.g., `bg-accent/70`), distinct from hover |
|
|
19
|
+
| **Disabled** | Action unavailable | Reduced opacity (`opacity-50`) + `cursor-not-allowed` + `aria-disabled` — but explain why somewhere |
|
|
20
|
+
| **Loading** | Async work in progress, no result yet | Shape-matched `Skeleton`, NOT a spinner unless duration is unknown and tiny |
|
|
21
|
+
| **Empty** | Loaded successfully, no data | `Empty` component with one sentence + one action that teaches the interface |
|
|
22
|
+
| **Error** | Load or action failed | Inline `Alert variant="destructive"` with cause + recovery, never just "Error" |
|
|
23
|
+
| **Optimistic** | User acted, we applied locally, server hasn't confirmed | Render the new state immediately + `toast` with undo |
|
|
24
|
+
| **Stale** | Cached data, may be outdated | Subtle "Updated 3 min ago" indicator + refresh affordance |
|
|
25
|
+
| **Success / Transient** | Action completed, briefly confirmed | `toast.success` (auto-dismiss) — NEVER a persistent banner |
|
|
26
|
+
|
|
27
|
+
## State-coverage requirements by surface type
|
|
28
|
+
|
|
29
|
+
Not every surface needs all 12. Use this matrix to know what's mandatory per surface.
|
|
30
|
+
|
|
31
|
+
| Surface | Required states | Common omissions to fix |
|
|
32
|
+
|---------|-----------------|------------------------|
|
|
33
|
+
| **Lists / tables** | idle, loading, empty, error | Empty (most missed), error |
|
|
34
|
+
| **Forms** | idle, focus, validation-error, submitting, success, server-error | Submitting state, server-error vs field-error |
|
|
35
|
+
| **Buttons** | idle, hover, focus, active, disabled, loading | Loading state (with spinner inside button) |
|
|
36
|
+
| **Inputs** | idle, focus, filled, invalid, disabled, read-only | Read-only vs disabled distinction |
|
|
37
|
+
| **Cards / items in a list** | idle, hover, focus, selected | Focus (keyboard navigation) |
|
|
38
|
+
| **Toggles / switches** | off, on, focus, disabled, indeterminate | Indeterminate for "mixed" bulk states |
|
|
39
|
+
| **Async data views** | loading, empty, populated, error, refreshing | Refreshing (vs full reload) |
|
|
40
|
+
| **File upload** | idle, dragging-over, uploading, success-each, error-each, complete | Dragging-over, per-file error |
|
|
41
|
+
| **Search** | idle, typing, debouncing, loading, results, no-results, error | Debouncing vs loading distinction |
|
|
42
|
+
| **Modals / sheets** | closed, opening, open, closing, blocked-by-pending-action | Blocked-by-pending (can't close mid-save) |
|
|
43
|
+
|
|
44
|
+
## The state machine — common transitions
|
|
45
|
+
|
|
46
|
+
Designing each state in isolation isn't enough. Design the **transition** between them.
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
fresh page load:
|
|
50
|
+
loading (skeleton, ~300ms min to avoid flash) → populated | empty | error
|
|
51
|
+
|
|
52
|
+
user adds item:
|
|
53
|
+
populated → optimistic (new item appears immediately, slightly faded)
|
|
54
|
+
→ confirmed (full opacity) | error (revert + toast)
|
|
55
|
+
|
|
56
|
+
user deletes item:
|
|
57
|
+
populated → optimistic (item slides out + toast w/ undo, 6s)
|
|
58
|
+
→ confirmed (gone) | undone (slides back)
|
|
59
|
+
|
|
60
|
+
user submits form:
|
|
61
|
+
idle → submitting (button shows spinner, form disabled)
|
|
62
|
+
→ success (toast + navigate | reset) | error (inline + keep values)
|
|
63
|
+
|
|
64
|
+
cached data view:
|
|
65
|
+
stale (with indicator) → user pulls to refresh → refreshing (spinner overlay, keep stale visible)
|
|
66
|
+
→ updated | error (keep stale, show error toast)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Mandatory rules
|
|
70
|
+
|
|
71
|
+
1. **No state may be a blank screen.** If loading is < 300ms, suppress the skeleton (use a delay). If > 300ms, show one. Never show whitespace for an indeterminate period.
|
|
72
|
+
2. **Empty is a design opportunity, not a failure.** Bad: "No items." Good: "No issues yet — create your first issue to start tracking." with a CTA.
|
|
73
|
+
3. **Errors must say what + what to do.** Bad: "An error occurred." Good: "Couldn't save changes — check your connection and try again." with a Retry button.
|
|
74
|
+
4. **Optimistic UI requires undo.** If you apply a destructive action immediately, the toast must offer undo for ≥ 6 seconds.
|
|
75
|
+
5. **Focus state is non-negotiable.** Removing the focus ring (`outline: none`) without replacing it is an accessibility violation. Use `focus-visible:ring-2 ring-ring ring-offset-2`.
|
|
76
|
+
6. **Disabled needs a reason.** A disabled button with no tooltip is a dead end. Either explain why (Tooltip on hover) or hide it.
|
|
77
|
+
7. **Loading should preview the shape.** `Skeleton` blocks should match the dimensions of the real content — text lines as text-height bars, images as aspect-ratio boxes.
|
|
78
|
+
8. **Success is transient.** Never leave a green success banner on screen — toast and dismiss. Only errors persist until acknowledged.
|
|
79
|
+
|
|
80
|
+
## State templates (copy these)
|
|
81
|
+
|
|
82
|
+
### List with all three async states
|
|
83
|
+
```tsx
|
|
84
|
+
{loading ? (
|
|
85
|
+
<ListSkeleton count={5} />
|
|
86
|
+
) : error ? (
|
|
87
|
+
<Alert variant="destructive">
|
|
88
|
+
<AlertTitle>Couldn't load items</AlertTitle>
|
|
89
|
+
<AlertDescription>{error.message}</AlertDescription>
|
|
90
|
+
<AlertAction onClick={retry}>Try again</AlertAction>
|
|
91
|
+
</Alert>
|
|
92
|
+
) : items.length === 0 ? (
|
|
93
|
+
<Empty>
|
|
94
|
+
<EmptyHeader>
|
|
95
|
+
<EmptyMedia variant="icon"><InboxIcon /></EmptyMedia>
|
|
96
|
+
<EmptyTitle>{emptyTitle}</EmptyTitle>
|
|
97
|
+
<EmptyDescription>{emptyDescription}</EmptyDescription>
|
|
98
|
+
</EmptyHeader>
|
|
99
|
+
<EmptyContent><Button onClick={onCreate}>{emptyCta}</Button></EmptyContent>
|
|
100
|
+
</Empty>
|
|
101
|
+
) : (
|
|
102
|
+
<ItemGroup>{items.map(...)}</ItemGroup>
|
|
103
|
+
)}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Button with loading state
|
|
107
|
+
```tsx
|
|
108
|
+
<Button disabled={isPending} onClick={submit}>
|
|
109
|
+
{isPending ? <Spinner className="size-4" /> : null}
|
|
110
|
+
{isPending ? "Saving…" : "Save"}
|
|
111
|
+
</Button>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Form field with validation
|
|
115
|
+
```tsx
|
|
116
|
+
<Field data-invalid={!!error}>
|
|
117
|
+
<FieldLabel htmlFor={id}>{label}</FieldLabel>
|
|
118
|
+
<Input id={id} aria-invalid={!!error} aria-describedby={`${id}-help`} />
|
|
119
|
+
{error ? (
|
|
120
|
+
<FieldError id={`${id}-help`}>{error}</FieldError>
|
|
121
|
+
) : (
|
|
122
|
+
<FieldDescription id={`${id}-help`}>{helpText}</FieldDescription>
|
|
123
|
+
)}
|
|
124
|
+
</Field>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Optimistic delete with undo
|
|
128
|
+
```tsx
|
|
129
|
+
function handleDelete(id) {
|
|
130
|
+
const item = items.find(i => i.id === id)
|
|
131
|
+
optimisticRemove(id)
|
|
132
|
+
toast.success(`Deleted "${item.name}"`, {
|
|
133
|
+
action: { label: "Undo", onClick: () => optimisticRestore(item) },
|
|
134
|
+
duration: 6000,
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## The state audit (run before shipping)
|
|
140
|
+
|
|
141
|
+
For every interactive surface on your view, walk through this list:
|
|
142
|
+
|
|
143
|
+
- [ ] What does it look like when there's no data?
|
|
144
|
+
- [ ] What does it look like while it's loading?
|
|
145
|
+
- [ ] What does it look like if loading fails?
|
|
146
|
+
- [ ] What does each interactive element look like on hover? on focus? when active? when disabled?
|
|
147
|
+
- [ ] What happens if the user submits/clicks while a previous action is still pending?
|
|
148
|
+
- [ ] What happens with very long content (long name, long description, 100 items)?
|
|
149
|
+
- [ ] What happens with very short content (1 character, 1 item)?
|
|
150
|
+
- [ ] Is success communicated? Is it transient (toast) or persistent (UI change)?
|
|
151
|
+
- [ ] Can the user undo destructive actions?
|
|
152
|
+
- [ ] Is there a focus indicator on every interactive element?
|
|
153
|
+
|
|
154
|
+
If any answer is "I haven't designed for that," the view isn't done.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nqui-theming
|
|
3
|
+
description: How to customize nqui's brand color, density, radius, and dark/light tokens without breaking the design rules. Use when a consumer asks "how do I make nqui match my brand?" or when adding a custom theme.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# nqui Theming — Brand Customization Without Breaking the Rules
|
|
7
|
+
|
|
8
|
+
nqui's defaults are opinionated. They are NOT the limit of what the kit can look like. This file is how you customize the kit while keeping the design rules (elevation, motion, hierarchy) intact.
|
|
9
|
+
|
|
10
|
+
## What you can change (safely)
|
|
11
|
+
|
|
12
|
+
| Token group | Where | Safe to override |
|
|
13
|
+
|-------------|-------|------------------|
|
|
14
|
+
| **Brand color** | `--primary-*` scale + `--primary` mapping | ✅ Full hue/chroma shift |
|
|
15
|
+
| **Radius scale** | `--radius` (base) | ✅ Affects all radius tokens |
|
|
16
|
+
| **Density** (control sizes) | `nqui-design-system/SKILL.md` size scale | ✅ via prop, NOT by changing token sizes |
|
|
17
|
+
| **Dark/light surface lightness** | `--background`, `--muted`, `--popover` | ✅ Within the 2+1 rule constraints |
|
|
18
|
+
| **Foreground softness** | `--foreground` (dark mode soft 0.92) | ✅ Per brand preference |
|
|
19
|
+
| **Sidebar direction** | `--sidebar` | ⚠️ Pick one direction for both modes |
|
|
20
|
+
| **Border weight** | `--border` lightness | ✅ Subtle adjustments |
|
|
21
|
+
| **Chart palette** | `--chart-1` through `--chart-5` | ✅ Categorical or sequential — your call |
|
|
22
|
+
|
|
23
|
+
## What you CANNOT change without breaking the philosophy
|
|
24
|
+
|
|
25
|
+
| Token | Why locked |
|
|
26
|
+
|-------|------------|
|
|
27
|
+
| **Surface count** (2+1 inline + 1 elevated) | Adding a `--surface-c` defeats `ELEVATION.md`'s entire premise |
|
|
28
|
+
| **Z-index scale** (`--z-*`) | Stacking conflicts cascade. Keep the semantic scale. |
|
|
29
|
+
| **Motion principle** (default: no motion, restrained when present) | Adding default transitions to everything = busy UI |
|
|
30
|
+
| **Shadow philosophy** (only on elevated surfaces) | Inline shadows turn flat surfaces into decorative noise |
|
|
31
|
+
| **The decision rules** (ToggleGroup vs RadioGroup, Dialog vs AlertDialog) | These are semantic, not aesthetic. Locked. |
|
|
32
|
+
|
|
33
|
+
If your brand "needs" a third surface or default shadows on cards, the brand expression should happen via **color + typography + radius**, not by relaxing the rules.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## How to override safely
|
|
38
|
+
|
|
39
|
+
### Brand color (the most common ask)
|
|
40
|
+
|
|
41
|
+
Override the primary scale in your app's CSS, after nqui's import:
|
|
42
|
+
|
|
43
|
+
```css
|
|
44
|
+
@import "@nqlib/nqui/index.css";
|
|
45
|
+
|
|
46
|
+
:root {
|
|
47
|
+
/* Replace nqui's blue (hue 240) with your brand hue */
|
|
48
|
+
--primary-100: oklch(0.655 0.11 30); /* your hue */
|
|
49
|
+
--primary-200: oklch(0.655 0.135 30);
|
|
50
|
+
--primary-300: oklch(0.655 0.155 30);
|
|
51
|
+
--primary-400: oklch(0.655 0.172 30);
|
|
52
|
+
--primary-500: oklch(0.605 0.215 30);
|
|
53
|
+
--primary-600: oklch(0.575 0.238 30);
|
|
54
|
+
--ring: var(--primary-500);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.dark {
|
|
58
|
+
--primary-100: oklch(0.30 0.14 30);
|
|
59
|
+
--primary-200: oklch(0.34 0.165 30);
|
|
60
|
+
--primary-300: oklch(0.385 0.185 30);
|
|
61
|
+
--primary-400: oklch(0.435 0.198 30);
|
|
62
|
+
--primary-500: oklch(0.515 0.168 30);
|
|
63
|
+
--primary-600: oklch(0.565 0.185 30);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Rules for picking your brand hue:**
|
|
68
|
+
- One hue. Don't override into two hues alternating — that breaks the focus + selection consistency.
|
|
69
|
+
- Match the L (lightness) values of nqui's defaults to keep contrast guarantees.
|
|
70
|
+
- OKLCH only — don't mix HSL/RGB here.
|
|
71
|
+
|
|
72
|
+
### Radius (rounder or sharper)
|
|
73
|
+
|
|
74
|
+
Change the single base — the scale derives from it:
|
|
75
|
+
|
|
76
|
+
```css
|
|
77
|
+
:root {
|
|
78
|
+
--radius: 0.25rem; /* sharper — closer to Stripe/GitHub */
|
|
79
|
+
/* OR */
|
|
80
|
+
--radius: 0.75rem; /* rounder — closer to Vercel/Linear */
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`--radius-sm/md/lg/xl/2xl` automatically follow.
|
|
85
|
+
|
|
86
|
+
### Density (compact vs comfortable)
|
|
87
|
+
|
|
88
|
+
**Don't change the token sizes** (h-6/h-7/h-8 are the design system contract). Instead, **default to a different size globally** via your component wrappers OR pass `size="sm"` as the project convention.
|
|
89
|
+
|
|
90
|
+
For an ultra-compact app: build a tiny `<UiProvider>` that defaults all interactive components to `size="sm"`. For a comfortable app, use defaults.
|
|
91
|
+
|
|
92
|
+
### Surface palette (warm paper vs cool gray vs pure)
|
|
93
|
+
|
|
94
|
+
```css
|
|
95
|
+
:root {
|
|
96
|
+
/* Cool gray (instead of warm paper) */
|
|
97
|
+
--background: oklch(0.985 0.002 250); /* hue 250 = cool, instead of 95 warm */
|
|
98
|
+
--muted: oklch(0.92 0.003 250);
|
|
99
|
+
--border: oklch(0.89 0.004 250);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
When you change the bg hue, **shift the semantic colors with it**:
|
|
104
|
+
- success hue 135 → maybe 130 (closer to cool palette)
|
|
105
|
+
- warning hue 80 → keep
|
|
106
|
+
- destructive hue 25 → maybe 20
|
|
107
|
+
- info hue 200 → keep or cut
|
|
108
|
+
|
|
109
|
+
See `colors.css` for the full set.
|
|
110
|
+
|
|
111
|
+
### Chart palette
|
|
112
|
+
|
|
113
|
+
Default is **categorical** (5 distinct hues). If your product visualizes ordinal data instead (severity, intensity), override with shades of primary:
|
|
114
|
+
|
|
115
|
+
```css
|
|
116
|
+
:root {
|
|
117
|
+
/* Sequential (intensity gradient) — use for ordinal data */
|
|
118
|
+
--chart-1: oklch(0.85 0.10 240); /* lightest blue */
|
|
119
|
+
--chart-2: oklch(0.75 0.14 240);
|
|
120
|
+
--chart-3: oklch(0.65 0.18 240);
|
|
121
|
+
--chart-4: oklch(0.55 0.22 240);
|
|
122
|
+
--chart-5: oklch(0.45 0.25 240); /* deepest blue */
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Document which type your project uses so consumers don't mix them.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Theme audit before shipping
|
|
131
|
+
|
|
132
|
+
Before publishing a custom-themed nqui app:
|
|
133
|
+
|
|
134
|
+
- [ ] Contrast check: `--foreground` on `--background` ≥ 4.5:1 (AA), `--muted-foreground` on `--muted` ≥ 4.5:1
|
|
135
|
+
- [ ] Hover state visible: `--accent` clearly distinct from `--muted` AND `--border`
|
|
136
|
+
- [ ] Focus ring visible: `--ring` clearly visible on `--background` and `--muted`
|
|
137
|
+
- [ ] Primary button readable: `--primary-foreground` on `--primary` ≥ 4.5:1
|
|
138
|
+
- [ ] Destructive button readable: `--destructive-foreground` on `--destructive` ≥ 4.5:1
|
|
139
|
+
- [ ] Dark mode flipped: every check above passes in `.dark` too
|
|
140
|
+
- [ ] Sidebar direction consistent: same relative-to-page direction in both modes
|
|
141
|
+
- [ ] Chart palette appropriate: categorical for categories, sequential for intensity — not mixed
|
|
142
|
+
|
|
143
|
+
Run `/audit` (or whatever your eval is) after the theme is in place.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Common branding requests + the nqui-friendly answer
|
|
148
|
+
|
|
149
|
+
| Request | Nqui-friendly answer |
|
|
150
|
+
|---------|----------------------|
|
|
151
|
+
| "Make it more colorful" | Use brand color in MORE places (active states, badges, accents) — don't add MORE colors |
|
|
152
|
+
| "Make cards stand out more" | More space + uppercase labels. Not shadow + border + hue shift. |
|
|
153
|
+
| "Make it feel premium" | Larger spacing scale, smaller font sizes, single restrained accent. NOT more flair. |
|
|
154
|
+
| "Make it more playful" | Rounder radius (`--radius: 0.75rem`), warmer hue, slightly more saturated accent. NOT bouncy motion. |
|
|
155
|
+
| "Make it feel like Linear / Notion / Stripe" | Pick ONE; study their actual choices (hue, density, surface use). Don't try to be all three. |
|
|
156
|
+
| "Add brand gradient backgrounds" | No. nqui doesn't do decorative gradients. If you need brand expression on a marketing page, do it there, not in product UI. |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## What NOT to do when theming
|
|
161
|
+
|
|
162
|
+
- ❌ Override component CSS directly (`button.nqui-button { background: red; }`). Use tokens.
|
|
163
|
+
- ❌ Add new surface tokens beyond the 3 (`--surface-d` etc.)
|
|
164
|
+
- ❌ Add default shadows to Card / Input / Button
|
|
165
|
+
- ❌ Replace `var(--radius-*)` with hardcoded `border-radius: 12px` in component overrides
|
|
166
|
+
- ❌ Add motion (transitions, animations) that wasn't there by default
|
|
167
|
+
- ❌ Override the focus ring to be invisible "for design" — accessibility violation
|
|
168
|
+
- ❌ Use `!important` to fight component styles — there's a better way (tokens or composition)
|
|
169
|
+
|
|
170
|
+
If you're tempted to do any of the above, the kit isn't right for your use case. That's fine — fork or use a different kit. Don't bend nqui out of shape.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Building a theme variant pack
|
|
175
|
+
|
|
176
|
+
If you want a true alternative theme (e.g., "Brand A theme" / "Brand B theme"), structure as:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
your-app/themes/
|
|
180
|
+
├── brand-a.css /* :root + .dark overrides */
|
|
181
|
+
├── brand-b.css
|
|
182
|
+
└── default.css /* explicit default */
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Load one at a time via `<link>` swap or `@import` based on environment. Don't try to ship all themes in one CSS file — token cascades get unpredictable.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Tracking customizations
|
|
190
|
+
|
|
191
|
+
Document every token your team overrides in a `THEME_OVERRIDES.md` in your project. Future engineers (and AI agents) need to know WHY a token is overridden, not just WHAT.
|
|
192
|
+
|
|
193
|
+
Example:
|
|
194
|
+
```md
|
|
195
|
+
## THEME_OVERRIDES
|
|
196
|
+
|
|
197
|
+
- `--primary-*`: hue 30 (orange) — match brand identity
|
|
198
|
+
- `--radius`: 0.25rem — sharper, more "fintech" feel
|
|
199
|
+
- `--background`: cool gray hue 250 — better contrast with brand orange
|
|
200
|
+
- `--info`: removed (we don't use info semantically)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
This is what makes a customized nqui maintainable.
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nqui-writing
|
|
3
|
+
description: UX copywriting rules — buttons, labels, error messages, empty states, microcopy. The single largest differentiator between AI-built and human-built UI. Read when writing ANY user-facing text in a component.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# nqui Writing — Voice, Copy, Microcopy
|
|
7
|
+
|
|
8
|
+
UX writing is design. "Project deleted" and "Got it — project deleted, undo in 6 seconds" use the same components but feel like different products. This file is the rule set. Read before writing any user-facing string.
|
|
9
|
+
|
|
10
|
+
## Voice principles
|
|
11
|
+
|
|
12
|
+
These are not stylistic suggestions — they're the rule set applied to every label, button, message, and toast.
|
|
13
|
+
|
|
14
|
+
1. **Specific over generic.** Bad: "An error occurred." Good: "Couldn't reach the server." Bad: "Are you sure?" Good: "Delete 412 issues and 1.2 GB of attachments?"
|
|
15
|
+
2. **Present tense, active voice.** Bad: "Your changes have been saved." Good: "Changes saved." Bad: "An email will be sent to you." Good: "We'll email you when it's done."
|
|
16
|
+
3. **Calm, never alarmist.** Bad: "ERROR! Failed to save!" Good: "Couldn't save — check your connection." Errors are user moments — don't make them feel punished.
|
|
17
|
+
4. **Address the user as "you," refer to system as "we" sparingly.** "You haven't created any issues yet." "We'll notify you when…" — but most copy needs no pronoun at all.
|
|
18
|
+
5. **No exclamation marks.** Almost never. They read as either alarmist or AI-cheery. The rare exception: legitimate celebration ("Welcome back!" after a long absence — and even then, optional).
|
|
19
|
+
6. **No emoji in UI chrome.** They become noise. Reserve for actual user content (chat, notes). Status icons are not emoji — they're SVGs from the icon set.
|
|
20
|
+
7. **Title case for buttons and titles. Sentence case for everything else.** Buttons: "Save Changes" or "Save changes"? **Sentence case** ("Save changes") is the modern default — Apple, Linear, Stripe all use it. Only use Title Case for proper page titles ("Workspace Settings" → "Workspace settings").
|
|
21
|
+
|
|
22
|
+
## Button labels
|
|
23
|
+
|
|
24
|
+
**Pattern: verb + object.** Always.
|
|
25
|
+
|
|
26
|
+
| ❌ Bad | ✅ Good | Why |
|
|
27
|
+
|--------|---------|-----|
|
|
28
|
+
| "OK" | "Got it" / "Save" / "Done" | "OK" is ambiguous about consequence |
|
|
29
|
+
| "Submit" | "Send invite" / "Create issue" / "Pay $42" | "Submit" never tells you what happens |
|
|
30
|
+
| "Yes" / "No" | "Delete project" / "Keep project" | Yes/No requires re-reading the question |
|
|
31
|
+
| "Confirm" | "Confirm deletion" / "Confirm purchase" | Confirm what? |
|
|
32
|
+
| "Continue" | "Continue to payment" / "Save and continue" | Continue to where? |
|
|
33
|
+
| "Cancel" (in destructive dialog) | "Cancel" is fine here — the action is clear | Cancel of the destructive action |
|
|
34
|
+
|
|
35
|
+
**Verb tense:** present imperative ("Save", "Delete", "Send"). Not past ("Saved"), not future ("Will save"), not gerund ("Saving" — that's a loading state, not a label).
|
|
36
|
+
|
|
37
|
+
**Length:** 1–3 words is the target. 4 only if the verb genuinely needs an object phrase ("Move to archive").
|
|
38
|
+
|
|
39
|
+
**Primary vs secondary verb pairs:**
|
|
40
|
+
- "Save" / "Cancel" — not "Save" / "Discard" (discard sounds more destructive than it is)
|
|
41
|
+
- "Delete" / "Keep" — destructive contrast, neutral keep
|
|
42
|
+
- "Send invite" / "Cancel" — verb+object on primary, single-word on escape
|
|
43
|
+
- "Publish" / "Save as draft" — both have meaning, no "Cancel" needed
|
|
44
|
+
|
|
45
|
+
## Error messages
|
|
46
|
+
|
|
47
|
+
**Pattern: what happened + why (if useful) + what to do.**
|
|
48
|
+
|
|
49
|
+
| ❌ Bad | ✅ Good |
|
|
50
|
+
|--------|---------|
|
|
51
|
+
| "Error." | "Couldn't save changes." |
|
|
52
|
+
| "Invalid input." | "Use a valid email address (e.g., name@company.com)." |
|
|
53
|
+
| "Network error." | "Couldn't reach the server — check your connection and try again." |
|
|
54
|
+
| "Permission denied." | "You don't have permission to edit this project. Ask the workspace admin to give you the editor role." |
|
|
55
|
+
| "500 Internal Server Error" | "Something went wrong on our end. We're looking into it — try again in a minute." |
|
|
56
|
+
|
|
57
|
+
**Rules:**
|
|
58
|
+
- Never use error codes in user-facing text (log them, don't show them — unless it's a developer tool)
|
|
59
|
+
- Don't blame the user even when it's their input ("Invalid email" → "Use a valid email like name@company.com")
|
|
60
|
+
- Don't apologize excessively. One "Sorry" max, usually none. Solve the problem instead.
|
|
61
|
+
- If the user can fix it, tell them how. If they can't, tell them what we're doing about it.
|
|
62
|
+
|
|
63
|
+
## Empty states
|
|
64
|
+
|
|
65
|
+
**Pattern: title (what's empty) + description (why this is normal and what to do) + CTA (the one action that fills it).**
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
❌ Bad: ✅ Good:
|
|
69
|
+
No items. No issues yet
|
|
70
|
+
Create your first issue to start tracking work.
|
|
71
|
+
[+ New issue]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Rules:**
|
|
75
|
+
- Never just say "Empty" or "No data" alone — that's a state, not a message.
|
|
76
|
+
- The CTA in the empty state should be the SAME action as the primary CTA above the list. Don't introduce new actions just for empty.
|
|
77
|
+
- For a filtered empty state ("no issues match your search"), give an escape route — "Clear filter" — not just an apology.
|
|
78
|
+
- Onboarding empty states (user's first time) can be longer and educational. Returning-user empty states (user deleted all their items) should be brief.
|
|
79
|
+
|
|
80
|
+
**Filtered vs onboarding empty states:**
|
|
81
|
+
```tsx
|
|
82
|
+
// Onboarding empty (no items ever)
|
|
83
|
+
<EmptyTitle>No issues yet</EmptyTitle>
|
|
84
|
+
<EmptyDescription>Create an issue to track bugs, features, or anything else you're working on.</EmptyDescription>
|
|
85
|
+
<Button>+ New issue</Button>
|
|
86
|
+
|
|
87
|
+
// Filter empty (items exist, just none match)
|
|
88
|
+
<EmptyTitle>No issues match "{searchTerm}"</EmptyTitle>
|
|
89
|
+
<EmptyDescription>Try a different search or clear the filter.</EmptyDescription>
|
|
90
|
+
<Button variant="outline" onClick={clearFilter}>Clear filter</Button>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Confirmation copy (destructive actions)
|
|
94
|
+
|
|
95
|
+
**Pattern: state the specific consequence, name the action button after the verb.**
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
❌ Bad: ✅ Good:
|
|
99
|
+
Title: "Are you sure?" Title: "Delete this project?"
|
|
100
|
+
Body: "This action cannot Body: "All 412 issues and 1.2 GB of
|
|
101
|
+
be undone." attachments will be removed.
|
|
102
|
+
This cannot be undone."
|
|
103
|
+
[Cancel] [OK] [Cancel] [Delete]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Rules:**
|
|
107
|
+
- Title is a question ending in "?". Body is a statement.
|
|
108
|
+
- Body must state the SPECIFIC stakes — count of items, name of thing being affected, time period. Generic "this cannot be undone" without specifics is noise.
|
|
109
|
+
- Primary button repeats the verb from the title ("Delete this project?" → "Delete")
|
|
110
|
+
- Escape button is always "Cancel", not "Keep" or "No"
|
|
111
|
+
|
|
112
|
+
## Toasts and transient messages
|
|
113
|
+
|
|
114
|
+
**Success toasts: state the result. Past tense, no exclamation.**
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
"Project archived" ← good
|
|
118
|
+
"✓ Successfully archived your project!" ← bad (icon redundant, redundant verb, exclamation)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Toast with undo:**
|
|
122
|
+
```
|
|
123
|
+
"Issue deleted" [Undo]
|
|
124
|
+
"Moved to archive" [Undo]
|
|
125
|
+
"3 items archived" [Undo]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Toast for async confirmation (started, not finished):**
|
|
129
|
+
```
|
|
130
|
+
"Generating report — we'll email you when it's ready"
|
|
131
|
+
"Invitation sent to alex@company.com"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Duration:**
|
|
135
|
+
- Success without action: 3 seconds
|
|
136
|
+
- Success with undo: 6 seconds (Gmail standard)
|
|
137
|
+
- Error: until dismissed (don't auto-dismiss errors)
|
|
138
|
+
- Info: 4 seconds
|
|
139
|
+
|
|
140
|
+
## Labels and microcopy
|
|
141
|
+
|
|
142
|
+
### Field labels
|
|
143
|
+
- **Sentence case, no colon.** "Email address" not "Email:"
|
|
144
|
+
- **Specific over generic.** "Work email" not "Email" (when you mean specifically work email)
|
|
145
|
+
- **Match the data being collected.** "Phone number" not "Phone" if you need a number
|
|
146
|
+
|
|
147
|
+
### Field descriptions (helper text)
|
|
148
|
+
- One sentence, no period needed
|
|
149
|
+
- Explain the consequence, not just the format
|
|
150
|
+
- Bad: "Maximum 50 characters"
|
|
151
|
+
- Good: "Shown in the sidebar and on invoices"
|
|
152
|
+
|
|
153
|
+
### Placeholders
|
|
154
|
+
- **Don't replace labels with placeholders.** Both serve different purposes — labels are persistent, placeholders are examples.
|
|
155
|
+
- Use placeholders for format examples: "you@company.com" not "Enter your email"
|
|
156
|
+
- Don't use placeholders for required-field markers — that's what asterisks (or "required") are for
|
|
157
|
+
|
|
158
|
+
### Time and date formatting
|
|
159
|
+
- **Relative for recent**: "2 minutes ago", "Yesterday", "Last Tuesday"
|
|
160
|
+
- **Absolute for old**: "May 17, 2026" or "May 17"
|
|
161
|
+
- **Show absolute on hover** for relative timestamps (use `Tooltip`)
|
|
162
|
+
- **24-hour for product UIs in international contexts**; 12-hour for US-only consumer
|
|
163
|
+
- Always include timezone for scheduled times: "3:00 PM PT"
|
|
164
|
+
|
|
165
|
+
### Numbers and units
|
|
166
|
+
- **Abbreviate large numbers**: 1,234 → 1.2k, 1,234,567 → 1.2M (in dense UIs)
|
|
167
|
+
- **Use thin spaces or commas for thousands**, never bare digits: `1,234` not `1234`
|
|
168
|
+
- **Always show units inline**: "12 MB", "42 issues", "3 days"
|
|
169
|
+
- **Round in summary, exact in detail**: "About 1.2k issues" in a header, "1,247 issues" in a stats panel
|
|
170
|
+
- **Use `tabular-nums` font feature** so numeric columns align
|
|
171
|
+
|
|
172
|
+
## Anti-patterns (the AI-flavored ones)
|
|
173
|
+
|
|
174
|
+
These are the copy patterns that immediately signal "AI wrote this":
|
|
175
|
+
|
|
176
|
+
| ❌ Anti-pattern | ✅ Use instead |
|
|
177
|
+
|----------------|---------------|
|
|
178
|
+
| "Welcome to your dashboard!" | "Dashboard" (let the page name itself) |
|
|
179
|
+
| "Let's get started!" | "Create a project to begin" |
|
|
180
|
+
| "Awesome!" / "Great job!" | (nothing — completion is its own reward) |
|
|
181
|
+
| "Effortlessly manage your..." | (cut the entire sentence) |
|
|
182
|
+
| "Powered by AI" / "Smart suggestions" | (let the feature speak for itself) |
|
|
183
|
+
| "Click here to..." | "Open settings" / button with verb |
|
|
184
|
+
| "Please" before every instruction | (cut "please" — direct is respectful in UI) |
|
|
185
|
+
| "Don't worry, your data is safe" | (don't introduce worry just to dismiss it) |
|
|
186
|
+
| Emoji in every label (📊 Analytics, 🎯 Goals) | (icon SVG, no emoji) |
|
|
187
|
+
| "Whoops!" / "Oops!" / "Yikes!" | "Couldn't [verb]" (neutral, specific) |
|
|
188
|
+
| "Loading..." / "Please wait..." | (use Skeleton matching the shape, no text) |
|
|
189
|
+
| "You have no items yet" | "No issues yet" (omit "you have") |
|
|
190
|
+
| Sentences ending in "..." | (use only for in-progress states like "Saving…") |
|
|
191
|
+
|
|
192
|
+
## The microcopy audit
|
|
193
|
+
|
|
194
|
+
Before shipping a view, read every string aloud:
|
|
195
|
+
|
|
196
|
+
- [ ] Does each button tell the user what will happen when clicked?
|
|
197
|
+
- [ ] Does each error message tell the user how to fix it?
|
|
198
|
+
- [ ] Does the empty state teach the interface or just apologize?
|
|
199
|
+
- [ ] Is there any "Please" or "Sorry" that could be cut?
|
|
200
|
+
- [ ] Is every "this/that/it" specific (named, not pronoun)?
|
|
201
|
+
- [ ] Any exclamation marks? Cut them unless legitimately celebratory.
|
|
202
|
+
- [ ] Any em-dashes in places where a colon or period would work? (Em-dashes are an AI signature — use them deliberately, not reflexively.)
|
|
203
|
+
- [ ] Does the copy read like a tired colleague wrote it, or like a marketing person wrote it? Tired colleague is right.
|
|
204
|
+
|
|
205
|
+
If anything reads as "trying too hard," cut it.
|