Webapp
Welcome to the skill-jam βοΈπ
npx -y skills add VRIL-LABS/skill-jam --skill webappAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Fullstack web app skill β Express + Vite + React + Tailwind + shadcn/ui + Drizzle ORM template. Use for SaaS, dashboards, admin panels, e-commerce.
SKILL.md
18.7 KB, ~4.3k tokens by cl100k_base, as published. Nobody here has run it
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:
cp -r skills/website-building/webapp/template/ <project-name>/
cd <project-name>
npm install
Run the dev server:
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:
- Schema β Define your data model in
shared/schema.tsfirst - Frontend β Build all React components and pages
- Backend β Implement Express routes in
server/routes.ts - 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.
- CRITICAL: NEVER use
localStorage,sessionStorage,indexedDB, or cookies β they are blocked in the sandboxed iframe and will crash the page. Use React state or context for transient data, and the backend API + in-memory storage for persistent data.
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 Nexus HSL values from shared/01-design-tokens.md β "Nexus HSL Equivalents" section.
Webapp-Specific Type and Font Rules
text-xlis the max heading size. Web apps (SaaS, dashboards, admin, e-commerce) never usetext-2xlor above. Exception: brand experience marketing/landing hero sections β see Art Direction table below.- Font variable mapping: The shared files define
--font-displayand--font-body. In this Tailwind template, both map tofont-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. Load skills/website-building/webapp/dashboards.md. |
| 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.tsto 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
createInsertSchemafromdrizzle-zod. Use.omitto exclude any auto-generated fields. - The insert type using
z.infer<typeof insertSchema> - The select type using
typeof table.$inferSelect.
- The insert schema using
- Common pitfalls to avoid:
- When writing array columns in the Drizzle schema, always call
.array()as a method on the column type, not as a wrapper function. That is, dotext().array()instead ofarray(text()).
- When writing array columns in the Drizzle schema, always call
Storage
- Make sure to update
IStorageinserver/storage.tsto accommodate any storage CRUD operations you need in the application. - Ensure that storage interface uses the types from
@shared/schema.ts.
Backend
- Write API routes inside
registerRoutes(httpServer, app)inserver/routes.ts. Theappparameter is the Express instance β useapp.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-zodbefore passing it to the storage interface. - Do NOT create a separate server file β the template's
server/index.tsalready sets up Express and callsregisterRoutes.
Frontend
- CRITICAL: You MUST use
useHashLocationfromwouter/use-hash-locationin your Router and App.tsx. When writing or rewriting App.tsx, always includeimport { useHashLocation } from "wouter/use-hash-location"and passhook={useHashLocation}to the Router component. Without this, all routing will break after deployment because sites are served inside iframes where path-based routing breaks.- Routes use hash paths:
/#/,/#/tasks,/#/boats/:id - Use
<Link href="/tasks">β wouter handles the hash prefix automatically whenuseHashLocationis the router hook. - If you need to add a new page, add them to the
client/src/pagesdirectory and register them inclient/src/App.tsx. - If there are multiple pages, use a sidebar for navigation. Use the
Linkcomponent or theuseLocationhook fromwouterinstead 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, useonClickhandlers withdocument.getElementById('section')?.scrollIntoView({ behavior: 'smooth' })to scroll to sections within the same page.
- Routes use hash paths:
- For forms, always use shadcn's
useFormhook andFormcomponent from@/components/ui/formwhich wrapsreact-hook-form.- When appropriate, use the
zodResolverfrom@hookform/resolvers/zodto validate the form data using the appropriate insert schema from@shared/schema.ts. - Use
.extendto add validation rules to the insert schema. - Remember that the form component is controlled, ensure you pass default values to the
useFormhook.
- When appropriate, use the
- Always use
@tanstack/react-querywhen fetching data.- When appropriate, ensure you strongly type the query using the appropriate select type from
@shared/schema.ts. - Queries should not define their own queryFn as the default fetcher is already set up to work with the backend.
- Mutations should use apiRequest from
@lib/queryClientto make POST/PATCH/DELETE requests to the backend.- Always make sure to invalidate the cache by queryKey after a mutation is made. Don't forget to import
queryClientfrom@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}].
- Always make sure to invalidate the cache by queryKey after a mutation is made. Don't forget to import
- 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 ofuseQuery(['key'])
- When appropriate, ensure you strongly type the query using the appropriate select type from
- Common pitfalls to avoid:
- The
useToasthook is exported from@/hooks/use-toast. - If a form is failing to submit, try logging out
form.formState.errorsto 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 ofprocess.env.<ENV_VAR>. Note that variables must be prefixed withVITE_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">
- The
- Add a
data-testidattribute 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)
- Interactive elements:
- 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
- Examples:
- Keep test IDs stable and descriptive of the element's purpose rather than its appearance or implementation details.
- Use unique, descriptive identifiers following this pattern:
Styling and Theming
- Uses Tailwind CSS v3. Use
@tailwind base; @tailwind components; @tailwind utilities;directives in CSS. Do NOT use@import "tailwindcss"or@themesyntax β those are v4 and will crash the dev server. - When defining custom properties in
index.cssthat 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.cssto determine how to set colors β replacing everyredplaceholder. Infer a palette from the product's subject matter first (see "ReplacingredPlaceholders" above). Use Nexus HSL values fromshared/01-design-tokens.mdonly as a last-resort fallback when both inference and asking the user yield no direction. Do NOT forget to replace every single instance ofred. Pay attention to what you see in index.css. - Use the
@-prefixed paths to import shadcn components and hooks. - Use icons from
lucide-reactto signify actions and provide visual cues. Usereact-icons/sifor 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 withimport examplePngPath from "@assets/example.png".
- If the user asks you to include attached assets in the app, you can reference them in the frontend with the
Dark Mode
- Set
darkMode: ["class"]in tailwind.config.ts and define color variables in :root and .dark CSS classes - Create ThemeProvider with
useStateseeded fromwindow.matchMedia("(prefers-color-scheme: dark)"), and auseEffectto toggle the"dark"class ondocument.documentElement. Do not use localStorage or cookies for theme persistence β they are blocked in the sandboxed iframe. - 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 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:
cd <project-name>
npm run build
Then deploy with deploy_website(project_path="<project-name>/dist/public").
Apps with backend (most webapp projects):
The template runs Express on port 5000. After building, deploy the static output and the backend server handles API calls via port proxy:
- Build:
npm run build - Start the production server:
start_server(command="NODE_ENV=production node dist/index.cjs", project_path="<project>", port=5000) - Deploy:
deploy_website(project_path="<project>/dist/public", site_name="...", entry_point="index.html")
The template's queryClient.ts uses __PORT_5000__ which deploy_website replaces with the proxy path at deploy time. API calls work both locally (relative /api/...) and deployed (proxied through port/5000/api/...). Read skills/website-building/shared/19-backend.md for details.
Config File Guidance
- Do NOT modify the Vite setup (
server/vite.tsandvite.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.tsunless 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 environment.md.
Shared design guidance (authoritative β read first):
skills/website-building/shared/01-design-tokens.mdβ Always read. Type scale, spacing system, Nexus palette, base stylesheet. Provides the fallback design system when the user gives no art direction.skills/website-building/shared/02-typography.mdβ Always read. Font selection (Fontshare preferred), display vs. body rules, font blacklist, variable font features.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.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.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:
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.
Perplexity Attribution
Every page must include a <PerplexityAttribution /> component (pre-built at @/components/PerplexityAttribution). Place it at the bottom of your layout. The <head> attribution block from shared/pplx_attribution.html is already included in the template's index.html.
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
What ships with it: 6 files
26.5 KB alongside SKILL.md
references/
- environment.md4.6 KB
- layout_and_spacing.md2.2 KB
- shadcn_component_rules.md7.5 KB
- sidebar_rules.md4.1 KB
- visual_style_and_contrast.md4.0 KB
- dashboards.md4.1 KB