agentleFS
ErikaAX08ErikaAX08/erikas-skills/frontend-architecture/SKILL.md

frontend-architecture

Skill6 starsChanged 2 months agoApache-2.0View on GitHub

Defines a Clean Architecture and reusable component structures for frontend. Especially useful when the user wants to organize pages, tables, lists, item cards, subpages, and modal dialogs in a predictable and maintainable way. Applies to React, Vue, Svelte, or similar frameworks.

15 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use backend-architecture, mobile-design and 5 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • GoldenWing-360Skill

    backend-architecture

    Design backends that survive redeploys, server reboots, and modest scaling. Covers stateless application servers, state placement (object storage, managed databases, Redis), immutable deploy artifacts, health checks, graceful shutdown, database migrations that don't lock the world, and the twelve-factor baseline. Invoke when designing a new backend, when uploads disappear after a redeploy, or when productionizing a vibe-coded prototype.

    Also in 3 projects that use this

  • xenitV1Skill

    mobile-design

    Mobile-first design thinking and decision-making for iOS and Android apps. Touch interaction, performance patterns, platform conventions. Teaches principles, not fixed values. Use when building React Native, Flutter, or native mobile apps.

    Also in 3 projects that use this

  • harrissohail6Skill

    database-design

    Database schema design and SQL best practices. Use when designing tables, writing queries, or optimizing database performance.

    Also in 3 projects that use this

  • xenitV1Skill

    clean-code

    Pragmatic coding standards - concise, direct, no over-engineering, no unnecessary comments

    Also in 3 projects that use this

  • AnthropicSkill

    architecture

    Create or evaluate an architecture decision record (ADR). Use when choosing between technologies (e.g., Kafka vs SQS), documenting a design decision with trade-offs and consequences, reviewing a system design proposal, or designing a new component from requirements and constraints.

    Also in 3 projects that use this

  • JetBrainsSkill

    webapp-testing

    Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.

    Also in 3 projects that use this

The file

Show its rules and text

We split this skill into the rules it gives an agent

16 rules. 0 are common, 10 are distinctive, and 6 are very specific to this project.

  • 0

    Common

    Most of the 13 edited versions we read keep these.

  • 10

    Distinctive

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When starting a new frontend project with professional structure

    line 13
  2. Distinctive:

    When the user mentions "clean architecture", "layers", "use cases"

    line 14
  3. Distinctive:

    If the project requires screens with lists, tables, cards, subpages, and modal dialogs

    line 15
  4. Distinctive:

    All pages and their subcomponents must follow this structure:

    line 91
  5. Distinctive:

    Every screen follows this strict composition order:

    line 95
  6. Very specific:

    Buttons: never contain logic; they only open a Dialog, navigate, or mount another module's component.

    line 110
  7. Distinctive:

    If a Dialog already exists for a given purpose (e.g.: a confirmation dialog, a generic form dialog, an entity create/edit dialog), reuse it …

    line 117
  8. Very specific:

    Before creating a Dialog, check if an equivalent one already exists in the module's components/ or in presentation/shared/components/.

    line 120
  9. Very specific:

    If the same Dialog is needed by more than one module, promote it to presentation/shared/components/ and import it from there.

    line 122
  10. Distinctive:

    Every table or list must be an independent component.

    line 176
  11. Distinctive:

    If a list with cards is used instead of a table, each item goes in its own component.

    line 221
  12. Very specific:

    If a page has a subpage (e.g.: ProductDetailPage inside the products module), it follows the same pattern: it's called Page, is composed of …

    line 238
  13. Distinctive:

    Subpages: follow exactly the same pattern as main pages.

    line 366
  14. Very specific:

    Every creation/editing form: must go inside a Dialog.

    line 367
  15. Very specific:

    Never duplicate a Dialog: if an equivalent Dialog already exists, reuse it.

    line 374
  16. Distinctive:

    When the user asks to create a new page, automatically generate:

    line 378

“Common” is measured: kept by at least 6 in 10 of the 13 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Frontend Architecture Skill

This skill establishes a standard architecture and a component pattern for frontend, based on Clean Architecture. Its goal is to guarantee consistency, maintainability, and separation of concerns in projects of any size.

When to use this skill?

  • When starting a new frontend project with professional structure
  • When the user mentions "clean architecture", "layers", "use cases"
  • If the project requires screens with lists, tables, cards, subpages, and modal dialogs
  • To maintain a consistent pattern across pages and components

Clean Architecture in Frontend (Summary)

The architecture is organized in concentric layers, where dependencies point inward:

presentation layer (UI)
         ↓
use cases layer (business logic)
         ↓
repositories layer (abstractions)
         ↓
entities layer (domain models)

Recommended folder structure (React example):

src/
├── domain/               # Innermost layer: entities
│   ├── entities/         # models (e.g.: Product, User)
│   └── repositories/     # repository interfaces (e.g.: IProductRepository)
├── application/          # use cases (grouped by module when many)
│   └── useCases/
│       ├── products/
│       │   ├── GetProductsUseCase.ts
│       │   ├── CreateProductUseCase.ts
│       │   └── DeleteProductUseCase.ts
│       └── users/
│           ├── GetUsersUseCase.ts
│           └── CreateUserUseCase.ts
├── infrastructure/       # concrete repository implementations
│   └── repositories/     # (e.g.: ApiProductRepository, LocalStorageRepository)
├── presentation/         # outer layer: modules organized by feature
│   ├── products/
│   │   ├── ProductsPage.tsx
│   │   └── components/
│   │       ├── ProductsTable.tsx
│   │       ├── ProductCard.tsx
│   │       └── CreateProductDialog.tsx
│   ├── users/
│   │   ├── UsersPage.tsx
│   │   └── components/
│   │       ├── UsersTable.tsx
│   │       └── CreateUserDialog.tsx
│   ├── components/       # UI library components (e.g.: shadcn/ui)
│   │   └── ui/           # shadcn generated components (button, dialog, table, ...)
│   ├── lib/              # UI library helpers (e.g.: shadcn's utils.ts -> cn())
│   │   └── utils.ts
│   ├── shared/           # shared components across modules
│   │   └── components/
│   ├── hooks/            # hooks grouped by module when many
│   │   ├── products/
│   │   │   └── useProducts.ts
│   │   └── users/
│   │       └── useUsers.ts
│   └── contexts/         # if applicable
└── config/               # global configurations

UI library rule (shadcn, etc.): Any UI/component library that scaffolds its own folders (such as shadcn/ui) must live inside presentation/, never at the root of src/. This includes its components (presentation/components/ui/), its lib/ helpers (e.g.: presentation/lib/utils.ts with the cn() function), and any other generated assets (styles, providers, config helpers). The reason: these are pure presentation concerns and must not leak into domain/, application/, or infrastructure/. Configure the library so its aliases point into presentation/ (e.g.: in components.json set "aliases.components": "@/presentation/components" and "aliases.utils": "@/presentation/lib/utils").

Rule: When a module grows in complexity (many use cases, hooks, components, repositories, etc.), all related files must be grouped in their own subfolder named after the module. This applies to any layer: useCases/products/, hooks/products/, repositories/products/, etc. Keep everything organized by module to maintain clarity at scale.

Mandatory Component Pattern

All pages and their subcomponents must follow this structure:

Standard composition hierarchy

Every screen follows this strict composition order:

Page
 └─ Main component (Table / List / Cards)   ← the primary content of the route
 └─ Action buttons                          ← buttons that trigger secondary UI
      ├─ open Dialogs (create / edit / confirm)
      └─ navigate to / mount other modules
 └─ Other components (Dialogs, panels, drawers, ...)

Rules of the hierarchy:

  • A Page orchestrates: it renders one main component (the table, list, or card grid) plus the buttons that open everything else.
  • Buttons never contain logic; they only open a Dialog, navigate, or mount another module's component.
  • Everything a button opens (Dialogs, drawers, secondary modules) is a separate component, composed by the Page, never inlined.

Reuse Dialogs — never duplicate

If a Dialog already exists for a given purpose (e.g.: a confirmation dialog, a generic form dialog, an entity create/edit dialog), reuse it instead of creating a new one.

  • Before creating a Dialog, check if an equivalent one already exists in the module's components/ or in presentation/shared/components/.
  • If the same Dialog is needed by more than one module, promote it to presentation/shared/components/ and import it from there.
  • Generic, repeated dialogs (e.g.: ConfirmDialog, FormDialog) must be parameterized via props (title, fields, callbacks) so a single component covers every case instead of duplicating near-identical dialogs.
  • Duplicating a Dialog that already exists is not allowed.

1. Page (main page)

A page is a component that contains the complete structure of a route. It never contains domain logic, only use case orchestration and composition of child components.

React example:

// presentation/products/ProductsPage.tsx
import { useProducts } from "../hooks/products/useProducts";
import { ProductsTable } from "./components/ProductsTable";
import { CreateProductDialog } from "./components/CreateProductDialog";
import { useState } from "react";

export function ProductsPage() {
  const { products, loading, addProduct, deleteProduct } = useProducts();
  const [isDialogOpen, setIsDialogOpen] = useState(false);

  return (
    <div className="p-6">
      <div className="flex justify-between items-center mb-4">
        <h1 className="text-2xl font-bold">Products</h1>
        <button
          onClick={() => setIsDialogOpen(true)}
          className="bg-blue-600 text-white px-4 py-2 rounded"
        >
          + Add product
        </button>
      </div>

      {loading ? (
        <div>Loading...</div>
      ) : (
        <ProductsTable products={products} onDelete={deleteProduct} />
      )}

      <CreateProductDialog
        open={isDialogOpen}
        onClose={() => setIsDialogOpen(false)}
        onSave={addProduct}
      />
    </div>
  );
}

2. Tables / Lists (separate component)

Every table or list must be an independent component. It receives data and callbacks as props. It never fetches directly.

Example:

// presentation/products/components/ProductsTable.tsx
interface Product {
  id: string;
  name: string;
  price: number;
}

interface Props {
  products: Product[];
  onDelete: (id: string) => void;
}

export function ProductsTable({ products, onDelete }: Props) {
  return (
    <table className="w-full border">
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
          <th>Actions</th>
        </tr>
      </thead>
      <tbody>
        {products.map((p) => (
          <tr key={p.id}>
            <td>{p.name}</td>
            <td>{p.price}</td>
            <td>
              <button onClick={() => onDelete(p.id)}>Delete</button>
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

3. Item Card / List item

If a list with cards is used instead of a table, each item goes in its own component.

// presentation/products/components/ProductCard.tsx
export function ProductCard({ product, onDelete }: Props) {
  return (
    <div className="border rounded p-4 shadow">
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <button onClick={() => onDelete(product.id)}>Delete</button>
    </div>
  );
}

4. Subpages

If a page has a subpage (e.g.: ProductDetailPage inside the products module), it follows the same pattern: it's called Page, is composed of components (tables, cards, dialogs), and is placed inside its module folder.

Example:

// presentation/products/ProductDetailPage.tsx
export function ProductDetailPage() {
  return (
    <div>
      <h1>Product Detail</h1>
      <ProductInfo />
      <RelatedProductsList />
    </div>
  );
}

5. Always use Dialogs (modals)

Any creation or editing form must go inside a Dialog (modal). The dialog is an independent component that receives open, onClose, onSave.

Example:

// presentation/products/components/CreateProductDialog.tsx
interface Props {
  open: boolean;
  onClose: () => void;
  onSave: (product: Omit<Product, "id">) => void;
}

export function CreateProductDialog({ open, onClose, onSave }: Props) {
  const [name, setName] = useState("");
  const [price, setPrice] = useState(0);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    onSave({ name, price });
    onClose();
    setName("");
    setPrice(0);
  };

  if (!open) return null;

  return (
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center">
      <div className="bg-white p-6 rounded-lg w-96">
        <h2>Create Product</h2>
        <form onSubmit={handleSubmit}>
          <input
            type="text"
            placeholder="Name"
            value={name}
            onChange={(e) => setName(e.target.value)}
            className="w-full border p-2 mb-2"
          />
          <input
            type="number"
            placeholder="Price"
            value={price}
            onChange={(e) => setPrice(Number(e.target.value))}
            className="w-full border p-2 mb-4"
          />
          <div className="flex justify-end gap-2">
            <button type="button" onClick={onClose}>
              Cancel
            </button>
            <button
              type="submit"
              className="bg-blue-600 text-white px-4 py-2 rounded"
            >
              Save
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

Hook to orchestrate use cases (optional but recommended)

Hooks act as a bridge between the UI and use cases. They contain state and call use cases.

// presentation/hooks/products/useProducts.ts
import { useEffect, useState } from "react";
import { GetProductsUseCase } from "../../../application/useCases/products/GetProductsUseCase";
import { CreateProductUseCase } from "../../../application/useCases/products/CreateProductUseCase";
import { DeleteProductUseCase } from "../../../application/useCases/products/DeleteProductUseCase";
import { Product } from "../../domain/entities/Product";

export function useProducts() {
  const [products, setProducts] = useState<Product[]>([]);
  const [loading, setLoading] = useState(true);

  const getProductsUseCase = new GetProductsUseCase(/* repository */);
  const createProductUseCase = new CreateProductUseCase(/* repository */);
  const deleteProductUseCase = new DeleteProductUseCase(/* repository */);

  useEffect(() => {
    getProductsUseCase
      .execute()
      .then(setProducts)
      .finally(() => setLoading(false));
  }, []);

  const addProduct = async (product: Omit<Product, "id">) => {
    const newProduct = await createProductUseCase.execute(product);
    setProducts((prev) => [...prev, newProduct]);
  };

  const deleteProduct = async (id: string) => {
    await deleteProductUseCase.execute(id);
    setProducts((prev) => prev.filter((p) => p.id !== id));
  };

  return { products, loading, addProduct, deleteProduct };
}

Non-negotiable Rules

  1. Pages NEVER contain domain logic or direct fetch.
  2. Tables/lists are separate components, they receive data via props.
  3. Item cards are also separate components.
  4. Subpages follow exactly the same pattern as main pages.
  5. Every creation/editing form must go inside a Dialog.
  6. Use cases are classes or pure functions that don't depend on the UI.
  7. Repositories are injected into use cases (dependency inversion).
  8. Each module has its own folder with its page and a components/ subfolder.
  9. When a module grows, all its related files (use cases, hooks, repositories, components, etc.) must be grouped in subfolders named after the module across all layers.
  10. UI libraries (shadcn/ui, etc.) and everything they generate — components (components/ui/), their lib/ helpers (utils.ts / cn()), styles and config helpers — always live inside presentation/, never at the root of src/ nor in domain/, application/, or infrastructure/.
  11. Composition hierarchy is fixed: Page → main component (Table/List/Cards) + buttons → the Dialogs / modules those buttons open. Buttons only open Dialogs, navigate, or mount other modules; they hold no logic.
  12. Never duplicate a Dialog: if an equivalent Dialog already exists, reuse it. Shared dialogs go in presentation/shared/components/ and generic ones (ConfirmDialog, FormDialog) are parameterized via props.

Quick Template for New Pages

When the user asks to create a new page, automatically generate:

  1. presentation/{module}/{Module}Page.tsx (with layout, title, add button, and hook usage)
  2. presentation/{module}/components/{Module}Table.tsx or {Module}List.tsx
  3. presentation/{module}/components/{Module}Dialog.tsx (for create/edit)
  4. presentation/hooks/use{Module}.ts (with corresponding use cases)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.