frontend-conventions
Skill234 starsChanged 3 months agoMITView on GitHub
Frontend convention reference (SvelteKit / Svelte 5). Auto-injected into frontend-aware agents - not user-invocable.
8 other projects use a version of this.
Adopted by
Most recently taken up by
- jbouder/skillstheir own version, first seen Nov 1, 2026
- leroyguillaume/claudetheir own version, first seen Oct 30, 2026
- cloudflare/cloudflare-ostheir own version, first seen Sep 21, 2026
- aylinmarie/code-working-agentstheir own version, first seen Aug 19, 2026
- Benjaming61001/skillstheir own version, first seen Aug 10, 2026
- ryokkon624/scrum-agent-basetheir own version, first seen Jul 9, 2026
Other versions8 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 8
- cloudflare/cloudflare-os★ 12ksame name
- mikemai2awesome/agent-skills★ 14same name
- leroyguillaume/claude★ 7same name
- Benjaming61001/skills★ 1same name
- jbouder/skills★ 1same name
- kimzeze/frontend-conventions★ 1same name
- aylinmarie/code-working-agents★ 0same name
- ryokkon624/scrum-agent-base★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository29 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- add-aspire-depSkill
- add-background-jobSkill
- add-ci-areaSkill
- add-email-templateSkill
- add-env-varSkill
- add-options-classSkill
- add-permissionSkill
- add-rate-limitSkill
- address-reviewSkill
- add-testSkill
- backend-conventionsSkill
- create-issueSkill
- create-prSkill
- create-releaseSkill
- gen-typesSkill
- infra-conventionsSkill
- init-verifySkill
- manage-file-storageSkill
- new-endpointSkill
- new-entitySkill
- new-featureSkill
- new-pageSkill
- polish-uiSkill
- review-dependabotSkill
- review-designSkill
- review-prSkill
- security-conventionsSkill
- verifySkill
The file
We split this skill into the rules it gives an agent
12 rules. 0 are common, 2 are distinctive, and 10 are very specific to this project.
0
Common
Most of the 6 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
10
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 62
If the backend doesn't provide data you need - propose the endpoint, don't work around it.
- Very specific: line 64
File uploads: Use native fetch() with FormData - not browserClient.
- Very specific: line 78
Every backend ProblemDetails carries a stable snake_case code (see ErrorMessages.cs) next to the English detail.
- Very specific: line 127
Use interface Props + destructure from $props():
- Distinctive: line 142
Import via barrel only:
- Very specific: line 150
Add via CLI: pnpm dlx shadcn-svelte@latest add <name>.
- Very specific: line 156
Use ms-*/me-*/ps-*/pe-*/start-*/end-*/text-start/text-end/border-s/border-e/gap-* instead of physical equivalents …
- Very specific: line 163
Size: Default size everywhere - no size="sm" or size="lg" overrides on action buttons
- Very specific: line 218
Add to the correct feature file in all locale directories (e.g., messages/en/auth.json).
- Very specific: line 236
Use native fetch() with FormData for file uploads.
- Very specific: line 238
Avatar URLs: If user.hasAvatar is true, construct /api/users/${user.id}/avatar?v=${version} where version is a $state variable bumped on …
- Very specific: line 276
Import server config from barrel ($lib/config)
“Common” is measured: kept by at least 6 in 10 of the 6 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.
Files
Frontend Conventions (SvelteKit / Svelte 5)
Project Structure
src/
├── lib/
│ ├── api/ # client.ts, error-handling.ts, mutation.ts, backend-monitor.ts, v1.d.ts (GENERATED)
│ ├── auth/ # auth.ts (getUser, logout), middleware.ts (token refresh)
│ ├── components/
│ │ ├── ui/ # shadcn (generated, customizable)
│ │ ├── auth/ # Login/register/reset flows, 2FA step, Turnstile
│ │ ├── layout/ # Header, sidebar, user nav, theme/language toggles
│ │ ├── profile/ # Profile forms, avatar, account details
│ │ ├── settings/ # Password, account deletion, activity log, 2FA management
│ │ ├── admin/ # User/role/job management, audit trail
│ │ ├── dashboard/ # Dashboard widgets
│ │ ├── oauth/ # OAuth provider linking
│ │ └── common/ # Small shared pieces (status indicator, etc.)
│ │ # For the live component inventory, list the barrels: $lib/components/*/index.ts
│ ├── config/ # i18n.ts (client-safe), server.ts (server-only - never export from barrel)
│ ├── state/ # .svelte.ts files only (cooldown, health, shake, theme, sidebar, shortcuts)
│ ├── types/index.ts # Type aliases from API schemas
│ └── utils/ # ui.ts (cn()), permissions.ts, audit.ts, platform.ts, roles.ts, jobs.ts
├── routes/
│ ├── (app)/ # Authenticated (redirect guard)
│ │ └── admin/ # Permission-guarded per page
│ ├── (public)/login/ # Redirect away if logged in
│ └── api/[...path]/ # Catch-all proxy to backend
├── messages/{locale}/*.json # i18n (per-feature: core, auth, admin, etc.)
└── styles/ # themes.css, tailwind.css, animations.css, base.css, utilities.cssAPI Client
Two layers: lib/api/ (auth-agnostic client factory) and lib/auth/ (all auth concerns).
| Export | Module | Purpose |
createApiClient(fetch?, baseUrl?, middleware?) | $lib/api | Creates typed openapi-fetch client. Server load functions pass fetch + url.origin. |
browserClient | $lib/api | Singleton for client-side code. Created bare - auth wired at runtime. |
initBrowserAuth(middleware) | $lib/api | Registers auth middleware on browserClient exactly once (idempotent guard). |
createAuthMiddleware(fetch, baseUrl, onAuthFailure?) | $lib/auth | 401 -> deduplicated refresh -> retry idempotent methods only. |
getUser(fetch, origin) | $lib/auth | Returns GetUserResult - distinguishes "not authenticated" from "backend unavailable". |
Auth middleware flow: 401 -> deduplicated refresh -> retry GET/HEAD/OPTIONS only. Non-idempotent methods return 401 to caller (prevents double-submission).
Type Generation
Type aliases in $lib/types/index.ts:
import type { components } from '$lib/api/v1';
export type User = components['schemas']['UserResponse'];If the backend doesn't provide data you need - propose the endpoint, don't work around it.
File uploads: Use native fetch() with FormData - not browserClient. The openapi-fetch typed client doesn't reliably handle multipart/File objects. After upload success, call invalidateAll() to refresh server data.
Error Handling
Generic Errors
import { getErrorMessage, browserClient } from '$lib/api';
const { response, error } = await browserClient.POST('/api/...', { body });
if (!response.ok) toast.error(getErrorMessage(error, m.fallback_message()));Translating Specific Errors
Every backend ProblemDetails carries a stable snake_case code (see ErrorMessages.cs) next to the English detail. Branch on code - never on detail text. Pass an ErrorMessagesByCode map to translate known codes; unknown codes fall back to detail, then title, then the fallback:
import { getErrorMessage } from '$lib/api';
const message = getErrorMessage(error, m.auth_login_error(), {
auth_login_invalid_credentials: m.auth_login_invalidCredentials,
auth_login_account_locked: m.auth_login_accountLocked
});Mutations (Validation + Rate Limiting)
import { browserClient, handleMutationError } from '$lib/api';
import { createCooldown, createFieldShakes } from '$lib/state';
const cooldown = createCooldown();
const fieldShakes = createFieldShakes();
let fieldErrors = $state<Record<string, string>>({});
const { response, error } = await browserClient.PATCH('/api/...', { body });
if (response.ok) {
toast.success(m.success());
} else {
handleMutationError(response, error, {
cooldown,
fallback: m.error(),
onValidationError(errors) {
fieldErrors = errors;
fieldShakes.triggerFields(Object.keys(errors));
}
});
}Rate-limited buttons should show countdown:
<Button disabled={isLoading || cooldown.active}>
{#if cooldown.active}{m.common_waitSeconds({ seconds: cooldown.remaining })}
{:else if isLoading}<Loader2 class="me-2 h-4 w-4 animate-spin" />{m.submit()}
{:else}{m.submit()}{/if}
</Button>Component Rules
Props
Use interface Props + destructure from $props():
<script lang="ts">
interface Props {
user: User;
onSave?: (data: FormData) => void;
class?: string;
}
let { user, onSave, class: className }: Props = $props();
</script>Organization
Import via barrel only:
import { ProfileForm, AvatarDialog } from '$lib/components/profile';shadcn
Add via CLI: pnpm dlx shadcn-svelte@latest add <name>. Check ui.shadcn.com before building custom UI. Convert physical CSS to logical in generated components.
Styling
Logical Properties Only
Use ms-*/me-*/ps-*/pe-*/start-*/end-*/text-start/text-end/border-s/border-e/gap-* instead of physical equivalents (ml-*/mr-*/pl-*/pr-*/left-*/right-*/text-left/text-right/border-l/border-r/space-x-*). Full mapping in the design tokens reference.
Button Layout (Action/Submit Buttons)
- Mobile:
w-full(full width, stacked vertically) - Desktop:
sm:w-auto(auto width, right-aligned) - Wrapper:
flex flex-col gap-2 sm:flex-row sm:justify-end - Size: Default size everywhere - no
size="sm"orsize="lg"overrides on action buttons
Responsive Design (Mobile-First)
- Base styles for 320px, then
sm:->md:->lg:->xl: - Touch targets minimum 44px (
min-h-11) on all interactive elements (CLAUDE.md hard rule) h-dvhnoth-screenfor full-height layoutsmin-w-0on flex children with text,shrink-0on icons/badges- Content grids:
lg:grid-cols-2- themax-w-7xlconstraint ensures sufficient column width even with the sidebar - Page content max-width:
max-w-7xl mx-auto- prevents ultra-wide layouts - Scale padding with breakpoints (
p-4 sm:p-6 lg:p-8) - Dialog grids: start with
grid-cols-1base - Min font:
text-xs(12px) - Animations: always
motion-safe:prefix
Theming
CSS variables in themes.css (:root + .dark), mapped in tailwind.css (@theme inline). Use cn() from $lib/utils for class merging.
Routing & Auth
hooks.server.ts -> +layout.server.ts (root: getUser) -> (app)/+layout.server.ts (503 if backend down, redirect if no user)
-> (public)/+layout.server.ts (503 if backend down)
-> login/+page.server.ts (redirect if user exists)Root layout fetches user once via getUser() which returns GetUserResult - distinguishes "not authenticated" from "backend unavailable".
Permission Guards
adminRoutesregistry ($lib/config/routes.ts): single source of truth pairing each admin path with its required permission- Admin layout: broad gate - derives permission list from
Object.values(adminRoutes) - Individual pages: specific permission check via
adminRoutes.X.permission-> redirect toroutes.dashboard - Sidebar/CommandPalette: filter items per-permission using
adminRoutes.X.permission - Backend is authoritative - frontend guards are UX only
// Route guards (server-side)
import { adminRoutes, routes } from '$lib/config';
if (!hasPermission(user, adminRoutes.users.permission)) throw redirect(303, routes.dashboard);
// UI permission checks (client-side)
import { hasPermission, Permissions } from '$lib/utils';
let canManage = $derived(hasPermission(data.user, Permissions.Users.Manage));Graceful Degradation for Secondary Fetches
Primary entity failure throws (hard error), but secondary data failures return empty arrays with a *LoadFailed flag. Components must consume the flag and show an Alert warning.
i18n
Keys: {domain}_{feature}_{element} (e.g., auth_login_title, profile_personalInfo_firstName).
Add to the correct feature file in all locale directories (e.g., messages/en/auth.json). Files: core, dashboard, auth, admin, jobs, audit, 2fa, oauth, avatars. Locales are configured in project.inlang/settings.json. Use: import * as m from '$lib/paraglide/messages'; m.key_name().
svelte-check reports ~32 paraglide module errors - these are expected (generated at build time). Ignore them.
State
.svelte.ts files in $lib/state/ only. Keep reactive state separate from pure utility functions.
| File | Exports |
cooldown.svelte.ts | createCooldown() - rate-limit countdown |
shake.svelte.ts | createShake(), createFieldShakes() |
theme.svelte.ts | getTheme(), setTheme(), toggleTheme() |
health.svelte.ts | healthState, initHealthCheck() - adaptive backend polling |
shortcuts.svelte.ts | shortcutsState, globalShortcuts() action, getAllShortcuts(), getShortcutSymbol() |
File Upload
Use native fetch() with FormData for file uploads.
Avatar URLs: If user.hasAvatar is true, construct /api/users/${user.id}/avatar?v=${version} where version is a $state variable bumped on avatar dialog close.
Security
Response Headers (hooks.server.ts)
X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy: camera=(), microphone=(), geolocation=(). HSTS in production only.
CSP (svelte.config.js)
Nonce-based script-src. style-src: unsafe-inline required for Svelte transitions. img-src: self https: data: blob:.
CSRF
API proxy validates Origin header on mutations. Same-origin + ALLOWED_ORIGINS env var allowed.
Testing
Co-locate tests: foo.ts -> foo.test.ts. Use vitest with explicit import { describe, it, expect, vi } from 'vitest'. Default environment is node; add // @vitest-environment jsdom for DOM tests. restoreMocks: true handles cleanup globally. See the /add-test skill for full setup, mock patterns, and test helpers.
pnpm run test # all tests (CI mode)
pnpm run test:watch # watch modeTypeScript Strictness
noUncheckedIndexedAccess: true- array/object index access returnsT | undefined. Guard before using.@typescript-eslint/no-explicit-any: 'error'-anyis a lint error. Useunknown, generics, or proper interfaces.
Anti-Patterns to Avoid
h-screen- useh-dvhxl:grid-cols-2for content - uselg:grid-cols-2- Unconstrained page content - always use
max-w-7xl mx-autowrapper size="sm"orsize="lg"on action/submit buttons - use default size withw-full sm:w-auto- Left-aligned action buttons - always right-align with
sm:justify-endwrapper null!,ascasts when narrowing works- Import server config from barrel (
$lib/config) - Hand-edit
v1.d.ts - Components in
$lib/components/root - use feature folders - Mix
.svelte.ts(reactive) with.ts(pure) - Build what shadcn already provides
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
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.

