Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Webapp

ASecurity

Load when building a full-stack browser application that needs server-side authentication, an API, a persistent database, or durable multi-user data. Do not load for a homepage, project board, informational site, browser game, or frontend-only interaction.

60 stars
0 votes
0 copies
0 views
Added 9/28/2026
ai-agentsrustgobashsqlreactnodeexpressspringtestingapi

Works with

cursorcliapi

Security Analysis

A96/100
mediumInstalls packages at runtime which could introduce malicious dependencies

Scanned 9/28/2026

Install to Claude Code

$npx -y skills add ufo-ai/ufo-core --skill webapp --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Webapp?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Webapp
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ufo-ai-webapp/badge)](https://www.skillsdirectory.com/skills/ufo-ai-webapp)

More formats (shields.io, HTML) on the badges page.

Files
SKILL.md
---
name: webapp
description: Load when building a full-stack browser application that needs server-side authentication, an API, a persistent database, or durable multi-user data. Do not load for a homepage, project board, informational site, browser game, or frontend-only interaction.
metadata:
  indexed: true
  depends:
  - website-building
---
# Fullstack Web App

Build fullstack web applications using an opinionated pre-wired template: Express + Vite + React + Tailwind CSS + shadcn/ui + Drizzle ORM.

## Getting Started

Copy the template to your project directory, then install dependencies. The skills tree is
read-only and `cp -r` carries a directory's mode onto the copy, so the project needs
`--no-preserve=mode` to be writable at all:

```bash
cp -r --no-preserve=mode template/ <project-name>/
cd <project-name>
npm install
```

Run the dev server:

```bash
npm run dev
```

This starts an Express server for the backend and a Vite server for the frontend on the same port.

## Build Order

Follow this order strictly:

1. **Schema** — Define your data model in `shared/schema.ts` first
2. **Frontend** — Build all React components and pages
3. **Backend** — Implement Express routes in `server/routes.ts`
4. **Integration** — Wire frontend to backend via queryClient

## Architecture

- Put as much of the app in the frontend as possible. The backend should only be responsible for data persistence and making API calls.
- Minimize the number of files. Collapse similar components into a single file.
- If the app is complex and requires functionality that can't be done in a single request, it is okay to stub out the backend and implement the frontend first.
- `localStorage`, `sessionStorage`, `indexedDB`, and cookies all work on the served origin — use them (or React state/context) for transient client state. Keep data that must persist server-side or across visitors in the backend API + SQLite database.
- The template ships with SQLite via `better-sqlite3` + Drizzle ORM for persistent server-side storage. Data survives server restarts. Use `sqliteTable` in `shared/schema.ts` and implement storage methods using the Drizzle query builder in `server/storage.ts`.

---

## Webapp Template — Design Notes

The shared design files (see **References** below) are the authoritative source for all design decisions — colors, fonts, type scale, spacing. This section only covers **template-specific workflow** that the shared files don't address.

### Replacing `red` Placeholders in `index.css`

The template's `index.css` ships with `red` placeholder values that must be replaced before the app looks right. **Infer a palette from the subject matter first** — a fitness tracker should feel energetic (bright accent, dark surfaces), a recipe app should feel warm (amber/terracotta tones), a finance dashboard should feel precise (cool neutrals, blue accent). Derive colors from the product's domain, not from a generic default.

When deriving a custom palette, use HSL values in `H S% L%` format (no `hsl()` wrapper) and maintain both `:root` and `.dark` variants following the same variable structure in `index.css`.

If the subject gives no clear color signal AND the user provided no direction after being asked, fall back to the HSL values from `$UFO_HOME/skills/website-building/shared/01-design-tokens.md` → "HSL Equivalents" section.

### Webapp-Specific Type and Font Rules

- **`text-xl` is the max heading size.** Web apps (SaaS, dashboards, admin, e-commerce) never use `text-2xl` or above. Exception: brand experience marketing/landing hero sections — see Art Direction table below.
- **Font variable mapping:** The shared files define `--font-display` and `--font-body`. In this Tailwind template, both map to `font-sans` — use bold/semibold weight for display territory, regular weight for body territory.

---

## Art Direction by Product Type

| Product Type              | Concept-Driven Direction                                                                                                                                           | Token Starting Points                                                                                                                 |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| **SaaS / productivity**   | A writing tool is calm and typographic. A project management tool is structured and efficient. A design tool is visual and spacious. Match personality to purpose. | Neutral surfaces. 1 accent. Body font that matches the product's character.                                                           |
| **Dashboard / analytics** | Finance dashboards demand precision and sobriety. Marketing dashboards can be warmer and more visual. The data's domain sets the tone.                             | Sans-serif + monospace for data. High-contrast. Read `dashboards.md` from this directory.                                             |
| **E-commerce**            | Luxury goods: muted surfaces, serif display, restrained accent. Kids' toys: warm, bright, rounded. Outdoor gear: earthy tones, rugged sans-serif.                  | Warm palette derived from product category. Strong CTA contrast.                                                                      |
| **Brand experience**      | A music streaming brand differs from an architecture studio. Derive everything from the brand.                                                                     | Display font at `--text-xl` in-app; `--text-2xl` ONLY for marketing/landing hero sections. 1-2 custom accent hues. Theatrical motion. |
| **Admin panel**           | Utilitarian, clear, efficient. A healthcare admin panel feels different from a developer tools panel.                                                              | Inter or DM Sans (loaded via CDN, not system fonts). Functional color only. Dense layout.                                             |

---

## Best Practices by App Type

### SaaS Products & Dashboards

- **Sidebar navigation** with collapsible sections and pinnable items
- **Dark mode as first-class** — many dashboard users work in low-light environments
- **Real-time updates** — WebSockets or SSE for live data. "Last updated: 2m ago" for non-live data
- **Export everything** — CSV, PDF, image for every chart and table
- **Role-based views** — admin vs member vs viewer with different permissions
- **Onboarding checklist** — persistent progress tracker for new users

### E-Commerce & Online Stores

- **Product pages:** Hero image (zoomable, multi-angle), price, "Add to Cart" above the fold, shipping info, reviews
- **Fast checkout.** Guest checkout always. Auto-fill. 3 steps max: Cart > Shipping > Payment
- **Faceted search** with real-time results. Filter by price, category, rating, availability
- **Cart persistence.** Survives page refresh and session
- **Trust signals.** Secure checkout badge, return policy, shipping estimates, reviews, payment logos
- **Mobile shopping.** Sticky "Add to Cart", swipeable images, Apple Pay / Google Pay

### Brand Experiences & Marketing Apps

- **Scroll-driven narrative.** GSAP ScrollTrigger for pinned sections, scrubbing, parallax
- **Full-screen immersive sections.** Hero moments with video, animation, or interactive 3D
- **Micro-interactions that reward exploration.** Hover effects, parallax, cursor-following
- **Performance despite richness.** Lazy-load heavy content. Intersection Observer. Compress media
- **Responsive storytelling.** Pinned horizontal scroll on desktop becomes vertical stack on mobile

---

## Types

- Always think through and generate the data model first in `shared/schema.ts` to ensure consistency between frontend and backend. Do this before writing any other code.
- Keep the data model as simple as possible (e.g. don't add createdAt and updatedAt fields unless it is strictly necessary).
- For each model, additionally write:
  - The insert schema using `createInsertSchema` from `drizzle-zod`. Use `.omit` to exclude any auto-generated fields.
  - The insert type using `z.infer<typeof insertSchema>`
  - The select type using `typeof table.$inferSelect`.
- Common pitfalls to avoid:
  - SQLite does not support array columns. Store lists as JSON text columns and parse them in application code.

## Storage

- Make sure to update `IStorage` in `server/storage.ts` to accommodate any storage CRUD operations you need in the application.
- Ensure that storage interface uses the types from `@shared/schema.ts`.
- The Drizzle `better-sqlite3` driver is **synchronous**. Queries must be terminated with `.get()` (single row) or `.all()` (array of rows). Do NOT destructure the query builder directly — `const [row] = db.select()...` will not work. Use `.get()` for single results and `.all()` for lists:
  ```ts
  db.select().from(users).where(eq(users.id, id)).get(); // User | undefined
  db.select().from(users).all(); // User[]
  db.insert(users).values(data).returning().get(); // User
  db.delete(users).where(eq(users.id, id)).run(); // { changes: number }
  ```

## Backend

- Write API routes inside `registerRoutes(httpServer, app)` in `server/routes.ts`. The `app` parameter is the Express instance — use `app.get()`, `app.post()`, etc. to define routes.
- Always use the storage interface to do any CRUD operations. Keep the routes as thin as possible.
- Validate the request body using Zod schemas from `drizzle-zod` before passing it to the storage interface.
- Do NOT create a separate server file — the template's `server/index.ts` already sets up Express and calls `registerRoutes`.

## Frontend

- CRITICAL: You MUST wrap `<Switch>` inside `<Router hook={useHashLocation}>` — the `hook` prop goes on `<Router>`, NOT on `<Switch>`. Put it on `<Switch>` and every route silently 404s. Hash routing is required, and a hosted site does not remove that: the site's origin serves whatever the sandbox serves, so a path like `/candidates/3` survives a reload only if the app's own server falls back to `index.html` — a static folder under `deploy_website` never does. Hash routing needs no fallback anywhere. The correct pattern:

  ```tsx
  import { Switch, Route, Router } from 'wouter';
  import { useHashLocation } from 'wouter/use-hash-location';

  <Router hook={useHashLocation}>
    <Switch>
      <Route path="/" component={Home} />
      <Route path="/settings" component={Settings} />
      <Route component={NotFound} />
    </Switch>
  </Router>;
  ```

  Do NOT pass `hook` to `<Switch>` — Switch ignores it and routing silently fails with 404.
  - Routes use hash paths: `/#/`, `/#/tasks`, `/#/boats/:id`
  - Use `<Link href="/tasks">` — wouter handles the hash prefix automatically when `useHashLocation` is the router hook.
  - If you need to add a new page, add them to the `client/src/pages` directory and register them in `client/src/App.tsx`.
  - If there are multiple pages, use a sidebar for navigation. Use the `Link` component or the `useLocation` hook from `wouter` instead of modifying the window directly.
  - NEVER use `href="#section"` anchor links for in-page navigation — hash routing intercepts these as route changes, causing a "not found" error. Instead, use `onClick` handlers with `document.getElementById('section')?.scrollIntoView({ behavior: 'smooth' })` to scroll to sections within the same page.

- For forms, always use shadcn's `useForm` hook and `Form` component from `@/components/ui/form` which wraps `react-hook-form`.
  - When appropriate, use the `zodResolver` from `@hookform/resolvers/zod` to validate the form data using the appropriate insert schema from `@shared/schema.ts`.
  - Use `.extend` to add validation rules to the insert schema.
  - Remember that the form component is controlled, ensure you pass default values to the `useForm` hook.
- Always use `@tanstack/react-query` when fetching data.
  - When appropriate, ensure you strongly type the query using the appropriate select type from `@shared/schema.ts`.
  - Prefer the default queryFn. If you must write a custom queryFn (e.g., for response parsing or query params), use `apiRequest` from `@/lib/queryClient` — NEVER use raw `fetch()` — for consistent error handling and base-URL resolution. The production Express server serves the client and the API from one origin, so requests use relative `/api/...` paths.
  - Use `apiRequest` from `@/lib/queryClient` for ALL HTTP requests to the backend (GET, POST, PATCH, DELETE) — both in queries and mutations.
    - Always make sure to invalidate the cache by queryKey after a mutation is made. Don't forget to import `queryClient` from `@lib/queryClient`!
    - For hierarchical or variable query keys use an array for cache segments so cache invalidation works properly. That is, do queryKey: ['/api/recipes', id] instead of queryKey: [`/api/recipes/${id}`].
  - Show a loading or skeleton state while queries (via `.isLoading`) or mutations (via `.isPending`) are being made
  - The template uses TanStack Query v5 which only allows the object form for query related functions. e.g. `useQuery({ queryKey: ['key'] })` instead of `useQuery(['key'])`
- Common pitfalls to avoid:
  - The `useToast` hook is exported from `@/hooks/use-toast`.
  - If a form is failing to submit, try logging out `form.formState.errors` to see if there are form validation errors for fields that might not have associated form fields.
  - DO NOT explicitly import React as the existing Vite setup has a JSX transformer that does it automatically.
  - Use `import.meta.env.<ENV_VAR>` to access environment variables on the frontend instead of `process.env.<ENV_VAR>`. Note that variables must be prefixed with `VITE_` in order for the env vars to be available on the frontend.
  - <SelectItem> will throw an error if it has no value prop. Provide a value prop like this <SelectItem value="option1">
- Add a `data-testid` attribute to every HTML element that users can interact with (buttons, inputs, links, etc.) and to elements displaying meaningful information (user data, status messages, dynamic content, key values).
  - Use unique, descriptive identifiers following this pattern:
    - Interactive elements: `{action}-{target}` (e.g., `button-submit`, `input-email`, `link-profile`)
    - Display elements: `{type}-{content}` (e.g., `text-username`, `img-avatar`, `status-payment`)
  - For dynamically generated elements (lists, grids, repeated components), append a unique identifier at the end: `{type}-{description}-{id}`
    - Examples: `card-product-${productId}`, `row-user-${index}`, `text-price-${itemId}`
    - The dynamic identifier can be any unique value (database ID, index, key) as long as it's unique within that group
  - Keep test IDs stable and descriptive of the element's purpose rather than its appearance or implementation details.

## Styling and Theming

- Uses Tailwind CSS v3. Use `@tailwind base; @tailwind components; @tailwind utilities;` directives in CSS. Do NOT use `@import "tailwindcss"` or `@theme` syntax — those are v4 and will crash the dev server.
- When defining custom properties in `index.css` that will be used by a tailwind config, always use H S% L% (space separated with percentages after Saturation and Lightness) (and do not wrap in hsl()).
  - For example:
    --my-var: 23 10% 23%;
- Analyze the comments inside of `index.css` to determine how to set colors — replacing every `red` placeholder. **Infer a palette from the product's subject matter first** (see "Replacing `red` Placeholders" above). Use the default palette's HSL values from `$UFO_HOME/skills/website-building/shared/01-design-tokens.md` only as a last-resort fallback when both inference and asking the user yield no direction. Do NOT forget to replace every single instance of `red`. Pay attention to what you see in index.css.
- Use the `@`-prefixed paths to import shadcn components and hooks.
- Use icons from `lucide-react` to signify actions and provide visual cues. Use `react-icons/si` for company logos.
- User may attach assets (images, etc.) in their request.
  - If the user asks you to include attached assets in the app, you can reference them in the frontend with the `@assets/...` import syntax.
  - For example, if the user attached asset is at `attached_assets/example.png`, you can reference it in the frontend with `import examplePngPath from "@assets/example.png"`.

## Dark Mode

1. Set `darkMode: ["class"]` in tailwind.config.ts and define color variables in :root and .dark CSS classes
2. Create ThemeProvider with `useState` seeded from `localStorage` (falling back to `window.matchMedia("(prefers-color-scheme: dark)")`), and a `useEffect` to toggle the `"dark"` class on `document.documentElement` and persist the choice to `localStorage`.
3. When not using utility class names configured in `tailwind.config.ts`, always use explicit light/dark variants for ALL visual properties: `className="bg-white dark:bg-black text-black dark:text-white"`. When using utility classes configured in tailwind config, you can assume these already been configured to automatically adapt to dark mode.

## Running the Project

Run `npm run dev` to start the development server. This starts Express for the backend and Vite for the frontend on the same port. After making edits, the server will automatically reload.

## Installing Packages

If you need to install additional packages, run `npm install <package-name>`.

## Testing

Read `$UFO_HOME/skills/website-building/shared/12-playwright-interactive.md` for testing and QA. Use Playwright via `js_repl` to navigate, interact with, and screenshot your local dev server.

## Deployment

**Static-only apps (no server logic):**

Build the frontend and deploy the static output:

```bash
cd <project-name>
npm run build
```

Then deploy with the `site` collection's `deploy_website` action: `object_action(kind="site", action="deploy_website", input={project_path="<project-name>/dist/public", site_name="...", entry_point="index.html"})`.

**Apps with backend (most webapp projects):**

The template's Express server serves the built client **and** the `/api/...` routes from one origin on port 5000. After building, run that server with `publish_website`:

1. Build: `npm run build`
2. Publish: `object_action(kind="site", action="publish_website", input={project_path="<project>", app_name="...", install_command="npm install", run_command="NODE_ENV=production node dist/index.cjs"})`

`publish_website` installs dependencies, runs the production server, and returns the reachable `http://localhost:<port>` URL inside the sandbox. A website-building subagent does not hold `publish_website`: build and validate through step 1, then stop and report that the app is built and needs publishing — the agent that delegated to you reads these same files and publishes them. Because client and API share one origin, requests use relative `/api/...` paths — no URL rewriting. Read `$UFO_HOME/skills/website-building/shared/19-backend.md` for details.

## Config File Guidance

- Do NOT modify the Vite setup (`server/vite.ts` and `vite.config.ts`) unless absolutely necessary. It is already configured to serve the frontend and backend on the same port and handles all the necessary setup for you. Don't add a proxy to the Vite server. All the aliases are already set up for you to import.
- Do NOT modify `drizzle.config.ts` unless absolutely necessary. It is pre-configured correctly.

## Environment

Read `references/environment.md` — pre-installed packages, dev server setup, deployment constraints.

## References

**Before writing code**, read the shared design files below (mandatory). Then identify whether any webapp-specific reference applies to the task. If it does, read it in parallel with `references/environment.md`.

**Shared design guidance (authoritative — read first):**

These files come from the `website-building` dependency and live under `$UFO_HOME/skills/website-building/shared/` — NOT under `shared/`.

- `$UFO_HOME/skills/website-building/shared/01-design-tokens.md` — **Always read.** Type scale, spacing system, the default palette, base stylesheet. Provides the fallback design system when the user gives no art direction.
- `$UFO_HOME/skills/website-building/shared/02-typography.md` — **Always read.** Font selection (Fontshare preferred), display vs. body rules, font blacklist, variable font features.
- `$UFO_HOME/skills/website-building/shared/03-motion.md` — **Read when the app has animation.** Easing blueprint (which curve for which context), duration quick-reference, spring presets, scroll-driven CLS rules, AnimatePresence patterns. The webapp elevation system for hover/active states is noted inside — the rest of the guidance (easing curves, timing, page transitions, stagger patterns) applies fully.
- `$UFO_HOME/skills/website-building/shared/05-taste.md` — **Read for any user-facing app.** Design taste principles: simplicity, fluidity, feedback, restraint. Defines the "feel" quality bar — progressive disclosure, context-preserving overlays, micro-interactions.
- `$UFO_HOME/skills/website-building/shared/08-standards.md` — **Always read.** Accessibility (WCAG AA, semantic HTML, keyboard nav), performance baselines, and AI aesthetic anti-patterns to avoid.

**Webapp-specific references:**

These files live under `references/`.

- `references/shadcn_component_rules.md` - Use when building or modifying UI with Shadcn components (especially Button, Card, Badge, Avatar, and Textarea).
- `references/layout_and_spacing.md` - Use when structuring page layouts, sections, spacing rhythm, and component alignment.
- `references/sidebar_rules.md` - Use when building or modifying a sidebar.
- `references/visual_style_and_contrast.md` - Use when choosing contrast, borders, shadows, pane/panel treatment, and hero image presentation.

## SEO

- Ensure every page has a unique, descriptive title tag (e.g., "Product Name - Category | Site Name")
- Add meta descriptions that summarize page content concisely
- Implement Open Graph tags for better social media sharing appearance

Attribution

ufo-aiufo-ai
View sourceMore from ufo-ai →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Ultra-compressed communication mode that cuts output tokens while keeping technical accuracy. Levels: lite, full, ultra and the wenyan variants. Use for /caveman, "caveman mode", "talk like caveman", "be brief" or "less tokens".

1074701 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

695601 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3351 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

691 votes

math-skill

A comprehensive mathematical reasoning skill for AI assistants — handles arithmetic to research-level problems with rigorous step-by-step reasoning, systematic verification, and transparent uncertainty handling

381 votes
View all in ai-agents →