ui-ux-master 1.2.1 → 1.5.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/README.md +160 -209
- package/SKILL.md +89 -47
- package/docs/package-publishing.md +5 -3
- package/index.cjs +3 -1
- package/index.d.ts +1 -1
- package/index.mjs +4 -1
- package/package.json +4 -3
- package/references/brand-method-card-based-discovery.md +285 -0
- package/references/brand-method-conversion-simplicity.md +276 -0
- package/references/brand-method-ecosystem-bundling.md +263 -0
- package/references/brand-method-editorial-brand-world.md +265 -0
- package/references/brand-method-enterprise-trust-hub.md +298 -0
- package/references/brand-method-playful-familiarity.md +284 -0
- package/references/brand-method-premium-restraint.md +270 -0
- package/references/brand-method-product-cinema.md +258 -0
- package/references/brand-method-technical-authority.md +290 -0
- package/references/brand-method-utility-command-center.md +278 -0
- package/references/color-psychology-branding.md +286 -0
- package/references/color-scale-system.md +347 -0
- package/references/competitive-landscape.md +32 -6
- package/references/design-discovery-protocol.md +171 -0
- package/references/design-system-schema.md +407 -0
- package/references/industry-reasoning-rules.md +504 -0
- package/references/landing-page-patterns.md +327 -0
- package/references/output-quality-gates.md +246 -0
- package/references/tech-stack-guidelines.md +636 -0
- package/references/ui-styles-catalog.md +552 -0
- package/references/visual-directions.md +362 -0
- package/tests/install-smoke.test.mjs +93 -12
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
# Tech Stack Guidelines
|
|
2
|
+
|
|
3
|
+
Load this file when the user specifies a frontend framework or tech stack. Apply the stack-specific rules on top of the design system tokens and component specs. Each section covers: component patterns, styling approach, accessibility patterns, and common pitfalls to avoid.
|
|
4
|
+
|
|
5
|
+
Default stack when none specified: **HTML + Tailwind CSS**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Stack Index
|
|
10
|
+
|
|
11
|
+
| # | Stack | Trigger words |
|
|
12
|
+
|---|-------|--------------|
|
|
13
|
+
| 1 | HTML + Tailwind CSS | "tailwind", "html", "vanilla", no stack specified |
|
|
14
|
+
| 2 | React + Tailwind | "react", "vite + react", "create react app" |
|
|
15
|
+
| 3 | Next.js | "next", "next.js", "nextjs", "app router", "pages router" |
|
|
16
|
+
| 4 | Vue 3 | "vue", "vue 3", "composition api" |
|
|
17
|
+
| 5 | Nuxt.js | "nuxt", "nuxt.js", "nuxt 3" |
|
|
18
|
+
| 6 | Angular | "angular", "ng" |
|
|
19
|
+
| 7 | Svelte / SvelteKit | "svelte", "sveltekit" |
|
|
20
|
+
| 8 | Astro | "astro" |
|
|
21
|
+
| 9 | Remix | "remix" |
|
|
22
|
+
| 10 | SolidJS | "solid", "solidjs" |
|
|
23
|
+
| 11 | React Native | "react native", "rn", "expo mobile" |
|
|
24
|
+
| 12 | Flutter | "flutter", "dart" |
|
|
25
|
+
| 13 | SwiftUI | "swiftui", "ios", "swift" |
|
|
26
|
+
| 14 | shadcn/ui | "shadcn", "shadcn/ui", "radix" |
|
|
27
|
+
| 15 | Jetpack Compose | "jetpack", "compose", "android" |
|
|
28
|
+
| 16 | Laravel (Blade + Tailwind) | "laravel", "blade", "php" |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 1 — HTML + Tailwind CSS
|
|
33
|
+
|
|
34
|
+
**Use for:** Static sites, landing pages, simple web apps, when user doesn't specify a framework.
|
|
35
|
+
|
|
36
|
+
### Component Pattern
|
|
37
|
+
```html
|
|
38
|
+
<!-- Button — Primary -->
|
|
39
|
+
<button
|
|
40
|
+
class="bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800
|
|
41
|
+
text-white font-medium px-5 py-2.5 rounded-lg
|
|
42
|
+
transition-colors duration-150
|
|
43
|
+
focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-offset-2
|
|
44
|
+
disabled:opacity-50 disabled:cursor-not-allowed"
|
|
45
|
+
type="button">
|
|
46
|
+
Get Started
|
|
47
|
+
</button>
|
|
48
|
+
|
|
49
|
+
<!-- Card -->
|
|
50
|
+
<div class="bg-white border border-neutral-200 rounded-xl p-6 shadow-sm hover:shadow-md transition-shadow">
|
|
51
|
+
<h3 class="text-lg font-semibold text-neutral-900">Card Title</h3>
|
|
52
|
+
<p class="mt-2 text-sm text-neutral-600">Card description text here.</p>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<!-- Input with label -->
|
|
56
|
+
<div class="space-y-1.5">
|
|
57
|
+
<label for="email" class="block text-sm font-medium text-neutral-700">Email</label>
|
|
58
|
+
<input
|
|
59
|
+
id="email" type="email" name="email"
|
|
60
|
+
class="w-full px-3.5 py-2.5 rounded-lg border border-neutral-300
|
|
61
|
+
text-sm text-neutral-900 placeholder:text-neutral-400
|
|
62
|
+
focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:border-indigo-400
|
|
63
|
+
disabled:bg-neutral-100 disabled:text-neutral-400"
|
|
64
|
+
placeholder="you@example.com" />
|
|
65
|
+
</div>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Rules
|
|
69
|
+
- Always use semantic HTML: `<button>` not `<div onclick>`. `<nav>`, `<main>`, `<section>`, `<article>` for structure.
|
|
70
|
+
- Never use Tailwind arbitrary value interpolation: `bg-[${color}]` is a runtime error — use full class names.
|
|
71
|
+
- CSS variables for brand tokens: define in `:root`, reference in Tailwind config via `extend.colors`.
|
|
72
|
+
- `prefers-reduced-motion`: wrap all transitions in `motion-safe:` variant or check via JS.
|
|
73
|
+
- Dark mode: use `dark:` prefix classes. Enable in `tailwind.config.js` with `darkMode: 'class'`.
|
|
74
|
+
|
|
75
|
+
### Common AI Mistakes to Avoid
|
|
76
|
+
- `text-shadow-*` — Tailwind has no text-shadow utility by default (needs plugin).
|
|
77
|
+
- `h-screen` on mobile — causes layout shift with dynamic viewport height. Use `min-h-dvh` instead.
|
|
78
|
+
- `target="_blank"` without `rel="noopener noreferrer"` — security vulnerability.
|
|
79
|
+
- `hover:` states without matching `focus-visible:` states — breaks keyboard navigation.
|
|
80
|
+
- Interpolated class names: `bg-${color}-500` — Tailwind purges these and they won't work.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 2 — React + Tailwind CSS
|
|
85
|
+
|
|
86
|
+
**Use for:** SPAs, interactive dashboards, component-heavy applications.
|
|
87
|
+
|
|
88
|
+
### Component Pattern
|
|
89
|
+
```tsx
|
|
90
|
+
// Button component
|
|
91
|
+
interface ButtonProps {
|
|
92
|
+
variant?: 'primary' | 'secondary' | 'ghost';
|
|
93
|
+
size?: 'sm' | 'md' | 'lg';
|
|
94
|
+
disabled?: boolean;
|
|
95
|
+
onClick?: () => void;
|
|
96
|
+
children: React.ReactNode;
|
|
97
|
+
'aria-label'?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const Button = ({ variant = 'primary', size = 'md', disabled, onClick, children, 'aria-label': ariaLabel }: ButtonProps) => {
|
|
101
|
+
const base = 'inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed';
|
|
102
|
+
const variants = {
|
|
103
|
+
primary: 'bg-indigo-600 hover:bg-indigo-700 text-white focus:ring-indigo-400',
|
|
104
|
+
secondary: 'bg-white hover:bg-neutral-50 text-neutral-900 border border-neutral-300 focus:ring-neutral-400',
|
|
105
|
+
ghost: 'hover:bg-neutral-100 text-neutral-700 focus:ring-neutral-400',
|
|
106
|
+
};
|
|
107
|
+
const sizes = {
|
|
108
|
+
sm: 'px-3 py-1.5 text-sm',
|
|
109
|
+
md: 'px-5 py-2.5 text-sm',
|
|
110
|
+
lg: 'px-6 py-3 text-base',
|
|
111
|
+
};
|
|
112
|
+
return (
|
|
113
|
+
<button
|
|
114
|
+
className={`${base} ${variants[variant]} ${sizes[size]}`}
|
|
115
|
+
disabled={disabled}
|
|
116
|
+
onClick={onClick}
|
|
117
|
+
aria-label={ariaLabel}
|
|
118
|
+
type="button">
|
|
119
|
+
{children}
|
|
120
|
+
</button>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Rules
|
|
126
|
+
- Use `clsx` or `cn()` (from shadcn) for conditional class merging — not template literals.
|
|
127
|
+
- All form inputs must have associated `<label>` (via `htmlFor`) or `aria-label`.
|
|
128
|
+
- Client state with `useState`/`useReducer`. Never fetch in render — use `useEffect` or React Query.
|
|
129
|
+
- List items always need `key` prop with stable IDs — not array index for dynamic lists.
|
|
130
|
+
- Accessible modals: use `role="dialog"`, `aria-modal="true"`, trap focus, close on Escape.
|
|
131
|
+
|
|
132
|
+
### Common AI Mistakes
|
|
133
|
+
- Missing `key` prop on list items (causes React reconciliation bugs).
|
|
134
|
+
- `useEffect` with missing dependencies (stale closures).
|
|
135
|
+
- Mutating state directly: `state.items.push(x)` — always return new objects/arrays.
|
|
136
|
+
- `onClick` on non-interactive elements without `role` and `tabIndex`.
|
|
137
|
+
- Not handling loading and error states in async components.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 3 — Next.js (App Router)
|
|
142
|
+
|
|
143
|
+
**Use for:** Production web apps, SEO-important pages, full-stack React applications.
|
|
144
|
+
|
|
145
|
+
### Component Pattern
|
|
146
|
+
```tsx
|
|
147
|
+
// app/components/ui/card.tsx — Server Component (default)
|
|
148
|
+
import { cn } from '@/lib/utils';
|
|
149
|
+
|
|
150
|
+
interface CardProps {
|
|
151
|
+
className?: string;
|
|
152
|
+
children: React.ReactNode;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function Card({ className, children }: CardProps) {
|
|
156
|
+
return (
|
|
157
|
+
<div className={cn('bg-white border border-neutral-200 rounded-xl p-6 shadow-sm', className)}>
|
|
158
|
+
{children}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Use 'use client' only when needed
|
|
164
|
+
'use client';
|
|
165
|
+
import { useState } from 'react';
|
|
166
|
+
export function Counter() {
|
|
167
|
+
const [count, setCount] = useState(0);
|
|
168
|
+
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Rules
|
|
173
|
+
- Default to **Server Components** — add `'use client'` only for interactivity, browser APIs, hooks.
|
|
174
|
+
- Use `<Image>` from `next/image` for all images — automatic optimization, lazy loading, `alt` required.
|
|
175
|
+
- Use `<Link>` from `next/link` for all internal navigation — never `<a href>` for same-domain.
|
|
176
|
+
- Metadata: export `metadata` object from each page for SEO — never set `<title>` in JSX directly.
|
|
177
|
+
- Route groups `(group)/` for layout sharing without URL segments.
|
|
178
|
+
- Loading UI: `loading.tsx` at each route level for streaming suspense.
|
|
179
|
+
- Error boundaries: `error.tsx` at each route level.
|
|
180
|
+
|
|
181
|
+
### Common AI Mistakes
|
|
182
|
+
- Mixing Server and Client component patterns (e.g., using `useState` in a Server Component).
|
|
183
|
+
- Forgetting `alt` on `<Image>` — required prop.
|
|
184
|
+
- Using `getServerSideProps` (Pages Router API) in App Router — doesn't exist.
|
|
185
|
+
- Not wrapping `useSearchParams()` in `<Suspense>`.
|
|
186
|
+
- Fetching the same data multiple times instead of using `fetch` with Next.js caching.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 4 — Vue 3 (Composition API)
|
|
191
|
+
|
|
192
|
+
**Use for:** Progressive web apps, teams coming from Vue 2, component-heavy applications.
|
|
193
|
+
|
|
194
|
+
### Component Pattern
|
|
195
|
+
```vue
|
|
196
|
+
<template>
|
|
197
|
+
<button
|
|
198
|
+
:class="[baseClasses, variantClasses[variant], sizeClasses[size]]"
|
|
199
|
+
:disabled="disabled"
|
|
200
|
+
type="button"
|
|
201
|
+
@click="$emit('click')">
|
|
202
|
+
<slot />
|
|
203
|
+
</button>
|
|
204
|
+
</template>
|
|
205
|
+
|
|
206
|
+
<script setup lang="ts">
|
|
207
|
+
interface Props {
|
|
208
|
+
variant?: 'primary' | 'secondary' | 'ghost'
|
|
209
|
+
size?: 'sm' | 'md' | 'lg'
|
|
210
|
+
disabled?: boolean
|
|
211
|
+
}
|
|
212
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
213
|
+
variant: 'primary',
|
|
214
|
+
size: 'md',
|
|
215
|
+
disabled: false,
|
|
216
|
+
})
|
|
217
|
+
defineEmits(['click'])
|
|
218
|
+
|
|
219
|
+
const baseClasses = 'inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed'
|
|
220
|
+
const variantClasses = {
|
|
221
|
+
primary: 'bg-indigo-600 hover:bg-indigo-700 text-white focus:ring-indigo-400',
|
|
222
|
+
secondary: 'border border-neutral-300 hover:bg-neutral-50 text-neutral-900 focus:ring-neutral-400',
|
|
223
|
+
ghost: 'hover:bg-neutral-100 text-neutral-700 focus:ring-neutral-400',
|
|
224
|
+
}
|
|
225
|
+
const sizeClasses = { sm: 'px-3 py-1.5 text-sm', md: 'px-5 py-2.5 text-sm', lg: 'px-6 py-3 text-base' }
|
|
226
|
+
</script>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Rules
|
|
230
|
+
- `<script setup>` syntax (Composition API) — not Options API for new code.
|
|
231
|
+
- `defineProps` with TypeScript generics for type-safe props.
|
|
232
|
+
- `v-for` always requires `:key` with stable, unique value.
|
|
233
|
+
- Use `computed()` for derived state, not methods that run on every render.
|
|
234
|
+
- `v-model` for two-way binding on form inputs.
|
|
235
|
+
- Avoid `v-html` — XSS risk. Use `DOMPurify` if unavoidable.
|
|
236
|
+
|
|
237
|
+
### Common AI Mistakes
|
|
238
|
+
- Using Options API (`data()`, `methods:`) when Composition API is available.
|
|
239
|
+
- Missing `:key` on `v-for`.
|
|
240
|
+
- Accessing DOM directly with `document.querySelector` — use `ref` instead.
|
|
241
|
+
- Mutating props directly — always emit events.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 5 — Nuxt.js 3
|
|
246
|
+
|
|
247
|
+
**Use for:** SSR/SSG sites, SEO-heavy applications, Vue-based full-stack.
|
|
248
|
+
|
|
249
|
+
### Rules
|
|
250
|
+
- `<NuxtLink>` for all internal navigation (not `<a>`).
|
|
251
|
+
- `<NuxtImg>` for images (requires `@nuxt/image` module).
|
|
252
|
+
- `useFetch()` / `useAsyncData()` for data fetching — not raw `fetch()` in setup.
|
|
253
|
+
- `useHead()` or `<Head>` component for meta tags on each page.
|
|
254
|
+
- Auto-imports: components, composables, and utils in their respective directories are auto-imported.
|
|
255
|
+
- `server/api/` for API routes (Nitro server).
|
|
256
|
+
|
|
257
|
+
### Common AI Mistakes
|
|
258
|
+
- Using Vue Router's `useRouter` instead of Nuxt's `useRouter` / `navigateTo`.
|
|
259
|
+
- Forgetting that Nuxt auto-imports — don't manually import components from `~/components/`.
|
|
260
|
+
- Using `<head>` directly in template — use `<Head>` component or `useHead()`.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## 6 — Angular
|
|
265
|
+
|
|
266
|
+
**Use for:** Enterprise applications, teams with Java/C# backgrounds, large-scale SPAs.
|
|
267
|
+
|
|
268
|
+
### Component Pattern
|
|
269
|
+
```typescript
|
|
270
|
+
// button.component.ts
|
|
271
|
+
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
|
272
|
+
import { CommonModule } from '@angular/common';
|
|
273
|
+
|
|
274
|
+
@Component({
|
|
275
|
+
selector: 'app-button',
|
|
276
|
+
standalone: true,
|
|
277
|
+
imports: [CommonModule],
|
|
278
|
+
template: `
|
|
279
|
+
<button
|
|
280
|
+
[class]="buttonClasses"
|
|
281
|
+
[disabled]="disabled"
|
|
282
|
+
(click)="clicked.emit()"
|
|
283
|
+
type="button">
|
|
284
|
+
<ng-content></ng-content>
|
|
285
|
+
</button>
|
|
286
|
+
`
|
|
287
|
+
})
|
|
288
|
+
export class ButtonComponent {
|
|
289
|
+
@Input() variant: 'primary' | 'secondary' | 'ghost' = 'primary';
|
|
290
|
+
@Input() disabled = false;
|
|
291
|
+
@Output() clicked = new EventEmitter<void>();
|
|
292
|
+
|
|
293
|
+
get buttonClasses(): string {
|
|
294
|
+
const base = 'inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed';
|
|
295
|
+
const variants: Record<string, string> = {
|
|
296
|
+
primary: 'bg-indigo-600 hover:bg-indigo-700 text-white focus:ring-indigo-400',
|
|
297
|
+
secondary: 'border border-neutral-300 hover:bg-neutral-50 text-neutral-900',
|
|
298
|
+
ghost: 'hover:bg-neutral-100 text-neutral-700',
|
|
299
|
+
};
|
|
300
|
+
return `${base} ${variants[this.variant]} px-5 py-2.5 text-sm`;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Rules
|
|
306
|
+
- Use standalone components (Angular 14+) — not `NgModule` for new code.
|
|
307
|
+
- `OnPush` change detection for performance-sensitive components.
|
|
308
|
+
- Use Angular's `HttpClient` not `fetch` — interceptors, error handling, type safety.
|
|
309
|
+
- Template-driven forms for simple; Reactive forms (`FormBuilder`) for complex.
|
|
310
|
+
- Use `AsyncPipe` in templates for observables — auto-unsubscribes.
|
|
311
|
+
- Accessibility: use CDK A11y (`FocusTrap`, `LiveAnnouncer`) for complex interactive patterns.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## 7 — Svelte / SvelteKit
|
|
316
|
+
|
|
317
|
+
**Use for:** Performance-critical sites, smaller bundle requirements, modern full-stack.
|
|
318
|
+
|
|
319
|
+
### Component Pattern
|
|
320
|
+
```svelte
|
|
321
|
+
<script lang="ts">
|
|
322
|
+
export let variant: 'primary' | 'secondary' | 'ghost' = 'primary';
|
|
323
|
+
export let disabled = false;
|
|
324
|
+
import { createEventDispatcher } from 'svelte';
|
|
325
|
+
const dispatch = createEventDispatcher();
|
|
326
|
+
</script>
|
|
327
|
+
|
|
328
|
+
<button
|
|
329
|
+
class={`inline-flex items-center justify-center font-medium rounded-lg transition-colors
|
|
330
|
+
focus:outline-none focus:ring-2 focus:ring-offset-2
|
|
331
|
+
disabled:opacity-50 disabled:cursor-not-allowed
|
|
332
|
+
${variant === 'primary' ? 'bg-indigo-600 hover:bg-indigo-700 text-white focus:ring-indigo-400 px-5 py-2.5' : ''}
|
|
333
|
+
${variant === 'secondary' ? 'border border-neutral-300 hover:bg-neutral-50 text-neutral-900 px-5 py-2.5' : ''}
|
|
334
|
+
${variant === 'ghost' ? 'hover:bg-neutral-100 text-neutral-700 px-5 py-2.5' : ''}`}
|
|
335
|
+
{disabled}
|
|
336
|
+
on:click={() => dispatch('click')}
|
|
337
|
+
type="button">
|
|
338
|
+
<slot />
|
|
339
|
+
</button>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Rules
|
|
343
|
+
- Reactive declarations with `$:` for derived values.
|
|
344
|
+
- `bind:` for two-way binding on form elements.
|
|
345
|
+
- Svelte transitions (`fade`, `slide`, `fly`) — always check `prefers-reduced-motion`.
|
|
346
|
+
- SvelteKit: use `load()` in `+page.ts` / `+page.server.ts` for data. Never fetch in component mount.
|
|
347
|
+
- Named slots for flexible component composition.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## 8 — Astro
|
|
352
|
+
|
|
353
|
+
**Use for:** Content sites, blogs, marketing pages, multi-framework islands.
|
|
354
|
+
|
|
355
|
+
### Rules
|
|
356
|
+
- Default to **zero JavaScript** — add `client:` directive only for interactive islands.
|
|
357
|
+
- `client:load` — hydrates immediately. `client:visible` — hydrates when in viewport. `client:idle` — hydrates when browser is idle.
|
|
358
|
+
- Use `<Image>` from `astro:assets` for optimized images.
|
|
359
|
+
- Content Collections for type-safe markdown/MDX content.
|
|
360
|
+
- `Astro.props` for component props, typed with `Props` interface.
|
|
361
|
+
- Can mix React, Vue, Svelte components — use the right tool for each island.
|
|
362
|
+
|
|
363
|
+
### Common AI Mistakes
|
|
364
|
+
- Adding `client:load` to every component — defeats Astro's performance benefit.
|
|
365
|
+
- Not using Content Collections for blog/docs content.
|
|
366
|
+
- Using React hooks in `.astro` files — not supported.
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## 9 — Remix
|
|
371
|
+
|
|
372
|
+
**Use for:** Full-stack React apps, progressive enhancement, form-heavy applications.
|
|
373
|
+
|
|
374
|
+
### Rules
|
|
375
|
+
- `loader()` for all data fetching — runs server-side, type-safe with `LoaderFunctionArgs`.
|
|
376
|
+
- `action()` for all mutations — replaces `fetch POST` from components.
|
|
377
|
+
- Forms submit to `action()` — works without JavaScript (progressive enhancement).
|
|
378
|
+
- `useLoaderData()` to consume loader data in components.
|
|
379
|
+
- Error boundaries: `ErrorBoundary` export on each route.
|
|
380
|
+
- Nested routes compose layouts automatically.
|
|
381
|
+
- Use `<Form>` from `@remix-run/react` not `<form>` for enhanced forms.
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## 10 — SolidJS
|
|
386
|
+
|
|
387
|
+
**Use for:** High-performance SPAs, fine-grained reactivity needs.
|
|
388
|
+
|
|
389
|
+
### Rules
|
|
390
|
+
- Reactive primitives: `createSignal`, `createMemo`, `createEffect`.
|
|
391
|
+
- No virtual DOM — updates are surgical. Don't think in "re-renders".
|
|
392
|
+
- `<For>` component instead of `.map()` for lists (fine-grained updates).
|
|
393
|
+
- `<Show>` instead of ternary for conditional rendering.
|
|
394
|
+
- `createStore` for complex nested state.
|
|
395
|
+
- Solid's JSX is different from React's — no `key` prop needed on `<For>`.
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## 11 — React Native (Expo)
|
|
400
|
+
|
|
401
|
+
**Use for:** Cross-platform iOS + Android apps.
|
|
402
|
+
|
|
403
|
+
### Component Pattern
|
|
404
|
+
```tsx
|
|
405
|
+
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
|
|
406
|
+
|
|
407
|
+
interface ButtonProps {
|
|
408
|
+
title: string;
|
|
409
|
+
onPress: () => void;
|
|
410
|
+
variant?: 'primary' | 'secondary';
|
|
411
|
+
disabled?: boolean;
|
|
412
|
+
accessibilityLabel?: string;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function Button({ title, onPress, variant = 'primary', disabled, accessibilityLabel }: ButtonProps) {
|
|
416
|
+
return (
|
|
417
|
+
<TouchableOpacity
|
|
418
|
+
style={[styles.base, styles[variant], disabled && styles.disabled]}
|
|
419
|
+
onPress={onPress}
|
|
420
|
+
disabled={disabled}
|
|
421
|
+
accessible={true}
|
|
422
|
+
accessibilityRole="button"
|
|
423
|
+
accessibilityLabel={accessibilityLabel ?? title}
|
|
424
|
+
accessibilityState={{ disabled }}>
|
|
425
|
+
<Text style={[styles.text, variant === 'secondary' && styles.textSecondary]}>{title}</Text>
|
|
426
|
+
</TouchableOpacity>
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const styles = StyleSheet.create({
|
|
431
|
+
base: { paddingHorizontal: 20, paddingVertical: 12, borderRadius: 8, alignItems: 'center', minHeight: 44 },
|
|
432
|
+
primary: { backgroundColor: '#4361EE' },
|
|
433
|
+
secondary: { backgroundColor: 'transparent', borderWidth: 1, borderColor: '#D1D5DB' },
|
|
434
|
+
disabled: { opacity: 0.5 },
|
|
435
|
+
text: { color: '#FFFFFF', fontSize: 15, fontWeight: '600' },
|
|
436
|
+
textSecondary: { color: '#111827' },
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Rules
|
|
441
|
+
- Minimum touch target: **44×44pt** (Apple HIG) — use `minHeight: 44, minWidth: 44`.
|
|
442
|
+
- Always provide `accessibilityLabel` and `accessibilityRole` on interactive elements.
|
|
443
|
+
- `TouchableOpacity` over `Pressable` for simpler cases; `Pressable` for complex press states.
|
|
444
|
+
- Use `StyleSheet.create()` — not inline objects (performance).
|
|
445
|
+
- `ScrollView` for short lists; `FlatList`/`FlashList` for long dynamic lists.
|
|
446
|
+
- `KeyboardAvoidingView` for forms.
|
|
447
|
+
- Safe area: use `useSafeAreaInsets()` or `SafeAreaView` — never hardcode status bar height.
|
|
448
|
+
|
|
449
|
+
### Common AI Mistakes
|
|
450
|
+
- Using CSS selectors/classes (not supported in RN).
|
|
451
|
+
- `<div>`, `<span>`, `<img>` — use `<View>`, `<Text>`, `<Image>`.
|
|
452
|
+
- Not handling keyboard avoid on form screens.
|
|
453
|
+
- Touch targets smaller than 44pt.
|
|
454
|
+
- Missing `accessibilityRole` on interactive elements.
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## 12 — Flutter
|
|
459
|
+
|
|
460
|
+
**Use for:** Cross-platform iOS + Android + web apps with native performance.
|
|
461
|
+
|
|
462
|
+
### Component Pattern
|
|
463
|
+
```dart
|
|
464
|
+
// Primary button widget
|
|
465
|
+
class AppButton extends StatelessWidget {
|
|
466
|
+
final String label;
|
|
467
|
+
final VoidCallback? onPressed;
|
|
468
|
+
final ButtonVariant variant;
|
|
469
|
+
|
|
470
|
+
const AppButton({
|
|
471
|
+
super.key,
|
|
472
|
+
required this.label,
|
|
473
|
+
this.onPressed,
|
|
474
|
+
this.variant = ButtonVariant.primary,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
@override
|
|
478
|
+
Widget build(BuildContext context) {
|
|
479
|
+
final theme = Theme.of(context);
|
|
480
|
+
return SizedBox(
|
|
481
|
+
height: 48,
|
|
482
|
+
child: ElevatedButton(
|
|
483
|
+
onPressed: onPressed,
|
|
484
|
+
style: ElevatedButton.styleFrom(
|
|
485
|
+
backgroundColor: variant == ButtonVariant.primary
|
|
486
|
+
? theme.colorScheme.primary
|
|
487
|
+
: Colors.transparent,
|
|
488
|
+
foregroundColor: variant == ButtonVariant.primary
|
|
489
|
+
? theme.colorScheme.onPrimary
|
|
490
|
+
: theme.colorScheme.primary,
|
|
491
|
+
side: variant == ButtonVariant.secondary
|
|
492
|
+
? BorderSide(color: theme.colorScheme.outline)
|
|
493
|
+
: null,
|
|
494
|
+
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
495
|
+
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
496
|
+
),
|
|
497
|
+
child: Text(label, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 15)),
|
|
498
|
+
),
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
enum ButtonVariant { primary, secondary }
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Rules
|
|
506
|
+
- Use `ThemeData` with `ColorScheme.fromSeed()` for consistent theming.
|
|
507
|
+
- `Material 3` (`useMaterial3: true`) for modern design language.
|
|
508
|
+
- Minimum touch target: 48×48dp — use `SizedBox` or `ConstrainedBox` to enforce.
|
|
509
|
+
- `Semantics` widget for custom accessibility labels.
|
|
510
|
+
- `const` constructors wherever possible — reduces rebuilds.
|
|
511
|
+
- `ListView.builder` for long lists — not `ListView` with `children:`.
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## 13 — SwiftUI
|
|
516
|
+
|
|
517
|
+
**Use for:** Native iOS, macOS, tvOS, watchOS, visionOS applications.
|
|
518
|
+
|
|
519
|
+
### Component Pattern
|
|
520
|
+
```swift
|
|
521
|
+
struct PrimaryButton: View {
|
|
522
|
+
let title: String
|
|
523
|
+
let action: () -> Void
|
|
524
|
+
var isDisabled: Bool = false
|
|
525
|
+
|
|
526
|
+
var body: some View {
|
|
527
|
+
Button(action: action) {
|
|
528
|
+
Text(title)
|
|
529
|
+
.font(.system(size: 15, weight: .semibold))
|
|
530
|
+
.foregroundColor(.white)
|
|
531
|
+
.frame(maxWidth: .infinity)
|
|
532
|
+
.padding(.vertical, 12)
|
|
533
|
+
.padding(.horizontal, 20)
|
|
534
|
+
}
|
|
535
|
+
.background(Color.accentColor)
|
|
536
|
+
.cornerRadius(8)
|
|
537
|
+
.disabled(isDisabled)
|
|
538
|
+
.opacity(isDisabled ? 0.5 : 1.0)
|
|
539
|
+
.accessibilityLabel(title)
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Rules
|
|
545
|
+
- Use `@State`, `@Binding`, `@StateObject`, `@ObservedObject`, `@EnvironmentObject` correctly.
|
|
546
|
+
- Follow Apple HIG: min 44×44pt touch target, Dynamic Type support required.
|
|
547
|
+
- `accessibilityLabel` and `accessibilityHint` on interactive elements.
|
|
548
|
+
- `@ViewBuilder` for conditional view composition.
|
|
549
|
+
- `SF Symbols` for icons — consistent with system UI.
|
|
550
|
+
- Support `preferredColorScheme` — do not hard-code light/dark colors.
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
## 14 — shadcn/ui (React + Radix)
|
|
555
|
+
|
|
556
|
+
**Use for:** React/Next.js apps requiring a pre-built accessible component library.
|
|
557
|
+
|
|
558
|
+
### Rules
|
|
559
|
+
- Install components with `npx shadcn-ui@latest add [component]` — they're copied to your project and owned by you.
|
|
560
|
+
- Customize via `tailwind.config.ts` theme extension and `globals.css` CSS variables.
|
|
561
|
+
- Base primitives come from Radix UI — keyboard navigation and ARIA already handled.
|
|
562
|
+
- Use `cn()` utility from `lib/utils.ts` for class merging.
|
|
563
|
+
- Override styles via `className` prop — never modify `components/ui/` files directly.
|
|
564
|
+
|
|
565
|
+
### CSS Variable Pattern (globals.css)
|
|
566
|
+
```css
|
|
567
|
+
@layer base {
|
|
568
|
+
:root {
|
|
569
|
+
--background: 0 0% 100%;
|
|
570
|
+
--foreground: 222.2 84% 4.9%;
|
|
571
|
+
--primary: 222.2 47.4% 11.2%;
|
|
572
|
+
--primary-foreground: 210 40% 98%;
|
|
573
|
+
--ring: 222.2 84% 4.9%;
|
|
574
|
+
--radius: 0.5rem;
|
|
575
|
+
}
|
|
576
|
+
.dark {
|
|
577
|
+
--background: 222.2 84% 4.9%;
|
|
578
|
+
--foreground: 210 40% 98%;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Common AI Mistakes
|
|
584
|
+
- Editing `components/ui/` files — defeats the copy-owned model.
|
|
585
|
+
- Using HSL shorthand wrong for shadcn variables (they're space-separated, not comma-separated).
|
|
586
|
+
- Not running `npx shadcn-ui init` first.
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
## 15 — Jetpack Compose (Android)
|
|
591
|
+
|
|
592
|
+
**Use for:** Native Android applications.
|
|
593
|
+
|
|
594
|
+
### Rules
|
|
595
|
+
- Stateless composables where possible — pass state down, events up.
|
|
596
|
+
- `remember {}` for local state, `rememberSaveable {}` for state that survives recomposition.
|
|
597
|
+
- `LazyColumn` / `LazyRow` for scrollable lists — not `Column` with map.
|
|
598
|
+
- Material 3 (`androidx.compose.material3`): `MaterialTheme.colorScheme`, `MaterialTheme.typography`.
|
|
599
|
+
- Minimum touch target: 48×48dp — `Modifier.sizeIn(minWidth = 48.dp, minHeight = 48.dp)`.
|
|
600
|
+
- `Modifier.semantics` for custom accessibility.
|
|
601
|
+
- Avoid side effects in composable functions — use `LaunchedEffect`, `SideEffect`, `DisposableEffect`.
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## 16 — Laravel (Blade + Tailwind)
|
|
606
|
+
|
|
607
|
+
**Use for:** PHP-based web apps, traditional server-rendered with Tailwind.
|
|
608
|
+
|
|
609
|
+
### Rules
|
|
610
|
+
- Use Blade components (`<x-button>`, `<x-card>`) for reusable UI.
|
|
611
|
+
- `{{ $variable }}` for escaped output (XSS safe). `{!! $html !!}` only for trusted HTML.
|
|
612
|
+
- CSRF: `@csrf` in every POST form — required.
|
|
613
|
+
- Livewire for reactive components without writing JavaScript.
|
|
614
|
+
- Alpine.js for lightweight interactivity in Blade.
|
|
615
|
+
- Vite for asset bundling (`vite.config.js` + `@vite()` directive).
|
|
616
|
+
- `route()` helper for all URL generation — never hardcode paths.
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
## Universal Rules (All Stacks)
|
|
621
|
+
|
|
622
|
+
Regardless of stack, always apply:
|
|
623
|
+
|
|
624
|
+
```
|
|
625
|
+
UNIVERSAL CHECKLIST
|
|
626
|
+
[ ] Semantic HTML structure (nav, main, section, article, aside, footer)
|
|
627
|
+
[ ] All images have meaningful alt text
|
|
628
|
+
[ ] All interactive elements reachable by keyboard (Tab order)
|
|
629
|
+
[ ] Focus states visible and not removed with outline: none
|
|
630
|
+
[ ] ARIA labels on icon-only buttons and custom interactive widgets
|
|
631
|
+
[ ] prefers-reduced-motion: wrap all CSS/JS animations
|
|
632
|
+
[ ] Color not the only differentiator for any meaning
|
|
633
|
+
[ ] Min touch target 44×44px (web) / 44pt (iOS) / 48dp (Android)
|
|
634
|
+
[ ] Error messages identify the field + state the recovery action
|
|
635
|
+
[ ] Loading states shown for async operations > 300ms
|
|
636
|
+
```
|