Skip to content
Development
Skill

/frontend-standards

MVVM architecture standards for React/TypeScript frontends with TanStack ecosystem, TailwindCSS, and Shadcn UI.

From plugin
sdd
4459 skills7 agents3 commands
Install
$ npx -y skills add LiorCohen/sdd --skill frontend-standards --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/frontend-standards

Context preview

The summary Claude sees to decide when to auto-load this skill.

MVVM architecture standards for React/TypeScript frontends with TanStack ecosystem, TailwindCSS, and Shadcn UI.

SKILL.md

frontend-standards.SKILL.md
name: frontend-standards
description: MVVM architecture standards for React/TypeScript frontends with TanStack ecosystem, TailwindCSS, and Shadcn UI.

Frontend Standards Skill

MVVM architecture for React/TypeScript frontends with strict separation between View, ViewModel, and Model layers.

---

Architecture: MVVM (Model-View-ViewModel)

View (React Components) → ViewModel (Hooks) → Model (Business Logic)
         ↓                       ↓                    ↓
    TailwindCSS            TanStack Query         Services/API
    Shadcn UI              useReducer+Context

Key Distinction: UI vs Logic

| Layer | Knows About | Example | |-------|-------------|---------| | **View** | UI rendering only | "Display user name in a card with blue border" | | **ViewModel** | State and handlers | "Fetch user, track loading, provide edit handler" | | **Model** | Business rules | "Format display name, check edit permissions" |

**Key Principle:** Views never contain business logic. ViewModels connect Views to Models. Models are framework-agnostic.

---

Directory Structure

src/
├── components/               # Shared presentational components
│   ├── user_card/
│   │   ├── index.ts          # Barrel exports only
│   │   ├── user_card.tsx
│   │   └── user_card.test.tsx
│   └── ui/                   # Shadcn UI primitives (see shadcn.md)
│       ├── index.ts
│       ├── button.tsx
│       ├── dialog.tsx
│       └── ...
├── hooks/                    # Shared hooks (auth, user data, etc.)
│   ├── index.ts
│   ├── use_auth.ts
│   ├── use_user_data.ts
│   └── ...
├── lib/                      # Pure utilities and helpers
│   ├── index.ts
│   ├── utils.ts              # cn() — clsx + tailwind-merge
│   └── ...
├── pages/                    # Page components (View + ViewModel + Model)
│   ├── home_page/
│   │   ├── index.ts
│   │   ├── home_page.tsx
│   │   ├── use_home_view_model.ts
│   │   ├── home_model.ts
│   │   └── home_page.test.tsx
│   └── user_profile/
│       ├── index.ts
│       ├── user_profile.tsx
│       ├── use_user_profile_view_model.ts
│       ├── user_profile_model.ts
│       └── user_profile.test.tsx
├── routes/                   # TanStack Router route definitions
│   ├── index.ts
│   └── routes.tsx            # createAppRouter() factory
├── services/                 # API clients and external services (flat)
│   ├── index.ts
│   ├── users.ts
│   ├── auth.ts
│   └── ...
├── types/                    # App-local type definitions
│   ├── index.ts
│   └── ...
└── index.ts                  # Entry point (only file with side-effects)

Directory Allowlist (D18)

Only the following `src/` subdirectories are permitted:

  • `components/` — shared presentational components
  • `components/ui/` — Shadcn UI primitives
  • `hooks/` — shared hooks (replaces `viewmodels/`)
  • `lib/` — pure utilities and helpers
  • `pages/` — page components (View + ViewModel + Model)
  • `routes/` — TanStack Router route definitions
  • `services/` — API clients and external services (flat, no subdirectories)
  • `types/` — app-local type definitions

**No new top-level `src/` directories.** If something doesn't fit, it belongs in one of the above.

---

Barrel-Only Index Files (D20)

All `index.ts` files must be **pure barrels** — imports and re-exports only. No logic, no side effects.

// src/hooks/index.ts — GOOD: pure barrel
export { useAuth } from './use_auth';
export { useUserData } from './use_user_data';
  • Always `.ts` (never `.tsx`) for index files
  • No function definitions, no variable assignments, no conditional logic

---

Barrel Imports Only (D23)

Every subdirectory has an `index.ts` barrel. All imports from outside a directory go through its barrel.

// GOOD: barrel import
import { useAuth } from '@/hooks';
import { cn } from '@/lib';
import { fetchUser } from '@/services';

// BAD: deep import
import { useAuth } from '@/hooks/use_auth';
import { cn } from '@/lib/cn';
import { fetchUser } from '@/services/users';

**Inside a module, nothing should ever import from its own `index.ts`.** All imports within a module must use relative paths. The barrel is the module's public API for external consumers only. For nested modules, the same barrel rules apply.

// In components/layout/layout.tsx:

// GOOD: relative path to sibling sub-module barrel
import { Sidebar } from '../sidebar';
import { Button } from '../ui';

// BAD: importing from own module's barrel — circular dependency
import { Sidebar } from '@/components';
import { Sidebar } from '@/components/sidebar';

---

No Side-Effects (D19)

Only `src/index.ts` (the app entry point) may have module-level side-effects (CSS import, `ReactDOM.render`, window assignment). All other files must be side-effect free.

// src/index.ts — OK: entry point, side-effects allowed
import './index.css';
import { createRoot } from 'react-dom/client';
import { App } from '@/components';

const root = createRoot(document.getElementById('root')!);
root.render(<App />);

// src/lib/utils.ts — GOOD: no side-effects, exports only
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));

---

Hooks for Providers (D22)

`QueryClient` and router instances must be lazily created via `useState` hooks inside provider components. No direct instantiation at module scope.

// GOOD: lazy creation inside component
import { useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

export const AppQueryProvider = ({ children }: { readonly children: React.ReactNode }) => {
  const [queryClient] = useState(() => new QueryClient({
    defaultOptions: { queries: { staleTime: 5 * 60 * 1000 } },
  }));

  return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
};

// BAD: module-scope instantiation (sid
Read more
Ships withsdd

Structure for AI-assisted development AI coding assistants are powerful but chaotic. You prompt, you get code, but then what?

Get the whole plugin

Other skills on sdd.