Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Frontend patterns for modern web frameworks, component libraries, auth flows, and analytics. Use when building UI components, creating pages, implementing auth flows, adding analytics events, or working with component libraries. Do NOT use for API-only or backend-only changes.
.claude/skills/bybren-llc-frontend-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | 35% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 60% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 48% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 111% | 0% |
Ensure consistent frontend development using established patterns for Next.js App Router, authentication, shadcn/ui components, and analytics.
typescript// SERVER COMPONENT (default) - Use for: // - Data fetching // - Auth checks // - SEO-critical content // app/dashboard/page.tsx import { auth } from "@clerk/nextjs/server"; export default async function DashboardPage() { const { userId } = await auth(); // Fetch data server-side... } // CLIENT COMPONENT - Use for: // - Interactivity (onClick, onChange) // - Browser APIs (localStorage, window) // - Hooks (useState, useEffect) // app/dashboard/_components/interactive-widget.tsx ("use client"); import { useState } from "react"; export function InteractiveWidget() { const [count, setCount] = useState(0); // Interactive logic... }
CRITICAL: Always use export const dynamic = 'force-dynamic' for authenticated pages:
typescript// app/dashboard/[page]/page.tsx import { auth } from "@clerk/nextjs/server"; import { redirect } from "next/navigation"; // REQUIRED - Auth context unavailable at build time export const dynamic = "force-dynamic"; export default async function ProtectedPage() { const { userId } = await auth(); if (!userId) { redirect("/sign-in"); } // Render protected content... }
textapp/ ├── (auth)/ # Auth routes (sign-in, sign-up) │ ├── sign-in/[[...sign-in]]/page.tsx │ └── sign-up/[[...sign-up]]/page.tsx ├── (marketing)/ # Public marketing pages │ ├── page.tsx # Homepage │ └── pricing/page.tsx ├── dashboard/ # Protected user area │ ├── page.tsx │ └── _components/ # Page-specific components └── admin/ # Admin-only area └── page.tsx
typescriptimport { auth } from "@clerk/nextjs/server"; export default async function Page() { const { userId } = await auth(); // userId is string | null }
typescript"use client" import { useUser, useAuth } from '@clerk/nextjs'; export function UserProfile() { const { user, isLoaded, isSignedIn } = useUser(); const { signOut } = useAuth(); if (!isLoaded) return <Skeleton />; if (!isSignedIn) return <SignInPrompt />; return <div>Welcome, {user.firstName}!</div>; }
typescript// app/admin/page.tsx import { auth } from "@clerk/nextjs/server"; import { redirect } from "next/navigation"; export const dynamic = "force-dynamic"; export default async function AdminPage() { const { userId, orgId, orgRole } = await auth(); if (!userId) { redirect("/sign-in"); } // Verify admin role const ADMIN_ORG_ID = process.env.CLERK_ADMIN_ORG_ID; const ADMIN_ROLE = "org:admin"; if (orgId !== ADMIN_ORG_ID || orgRole !== ADMIN_ROLE) { redirect("/admin-denied"); } // Render admin content... }
typescript// Always use @/components/ui path alias import { Button } from "@/components/ui/button"; import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form";
typescript"use client" import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { Button } from '@/components/ui/button'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; const FormSchema = z.object({ email: z.string().email('Invalid email'), name: z.string().min(1, 'Name is required'), }); type FormData = z.infer<typeof FormSchema>; export function MyForm() { const form = useForm<FormData>({ resolver: zodResolver(FormSchema), defaultValues: { email: '', name: '' }, }); async function onSubmit(data: FormData) { // Handle submission... } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <FormField control={form.control} name="name" render={({ field }) => ( <FormItem> <FormLabel>Name</FormLabel> <FormControl> <Input {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit">Submit</Button> </form> </Form> ); }
typescript// Primary action <Button>Save Changes</Button> // Secondary action <Button variant="secondary">Cancel</Button> // Destructive action <Button variant="destructive">Delete</Button> // Ghost/subtle <Button variant="ghost">Learn More</Button> // Link style <Button variant="link" asChild> <Link href="/docs">Documentation</Link> </Button> // Loading state <Button disabled={isLoading}> {isLoading ? 'Saving...' : 'Save'} </Button>
Use snake_case with category prefix:
typescript// User actions "user_signed_up"; "user_signed_in"; "user_profile_updated"; // Feature usage "feature_dark_mode_toggled"; "feature_export_clicked"; // Payments "payment_checkout_started"; "payment_completed"; "subscription_upgraded"; // Navigation "page_viewed"; "cta_clicked";
typescript"use client" import { usePostHog } from 'posthog-js/react'; export function TrackableButton() { const posthog = usePostHog(); function handleClick() { posthog?.capture('cta_clicked', { button_text: 'Get Started', page: '/pricing', variant: 'primary', }); } return <Button onClick={handleClick}>Get Started</Button>; }
focus:ring-2)typescript// Accessible button <Button aria-label="Close dialog"> <X className="h-4 w-4" /> </Button> // Accessible form field <FormItem> <FormLabel htmlFor="email">Email</FormLabel> <FormControl> <Input id="email" type="email" aria-describedby="email-error" /> </FormControl> <FormMessage id="email-error" /> </FormItem> // Skip link for keyboard users <a href="#main-content" className="sr-only focus:not-sr-only"> Skip to main content </a>
typescript// Mobile-first approach <div className=" px-4 // Mobile: 16px padding md:px-6 // Tablet: 24px padding lg:px-8 // Desktop: 32px padding "> // Responsive grid <div className=" grid grid-cols-1 // Mobile: 1 column md:grid-cols-2 // Tablet: 2 columns lg:grid-cols-3 // Desktop: 3 columns gap-4 "> // Hide/show at breakpoints <div className="hidden md:block">Desktop only</div> <div className="md:hidden">Mobile only</div>
typescript// Missing 'use client' for interactive components import { useState } from 'react'; // Will error! // Using hooks in server components export default async function Page() { const [state, setState] = useState(); // Will error! } // Missing force-dynamic on auth pages export default async function ProtectedPage() { const { userId } = await auth(); // May fail at build! } // Direct DOM manipulation document.getElementById('foo'); // Use refs instead // Inline styles (use Tailwind) <div style={{ marginTop: '20px' }}> // Use className="mt-5"
typescript// Proper client component "use client" import { useState } from 'react'; // Server component with auth export const dynamic = 'force-dynamic'; export default async function Page() { const { userId } = await auth(); } // Use refs for DOM access const inputRef = useRef<HTMLInputElement>(null); // Tailwind classes <div className="mt-5">
patterns_library/ui/components/ui/ (shadcn/ui)config/features.ts| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | pass→pass | 13,846 | 13,275 | -4% | 1 | 1 | 0% | 2,787 | 5,267 | +89% | 0 | 0 | — |
case-11 | fail→pass | 16,120 | 9,662 | -40% | 1 | 1 | 0% | 3,185 | 4,293 | +35% | 0 | 0 | — |
case-20 | pass→pass | 14,792 | 6,361 | -57% | 1 | 1 | 0% | 2,390 | 3,575 | +50% | 0 | 0 | — |
case-03 | fail→pass | 14,572 | 9,929 | -32% | 1 | 1 | 0% | 2,837 | 4,534 | +60% | 0 | 0 | — |
case-04 | pass→pass | 9,993 | 10,156 | +2% | 1 | 1 | 0% | 2,057 | 4,645 | +126% | 0 | 0 | — |
case-01 | fail→pass | 11,862 | 5,451 | -54% | 1 | 1 | 0% | 2,339 | 3,463 | +48% | 0 | 0 | — |
case-02 | fail→pass | 16,049 | 9,515 | -41% | 1 | 1 | 0% | 3,264 | 4,423 | +36% | 0 | 0 | — |
case-06 | pass→pass | 8,735 | 6,095 | -30% | 1 | 1 | 0% | 1,542 | 3,541 | +130% | 0 | 0 | — |
case-07 | fail→pass | 10,714 | 8,551 | -20% | 1 | 1 | 0% | 1,914 | 4,029 | +111% | 0 | 0 | — |
case-08 | pass→pass | 7,942 | 7,546 | -5% | 1 | 1 | 0% | 1,471 | 3,905 | +165% | 0 | 0 | — |
case-09 | fail→pass | 14,480 | 5,774 | -60% | 1 | 1 | 0% | 2,776 | 3,497 | +26% | 0 | 0 | — |
case-10 | pass→pass | 6,210 | 1,947 | -69% | 1 | 1 | 0% | 1,053 | 2,736 | +160% | 0 | 0 | — |
case-12 | fail→pass | 14,053 | 6,654 | -53% | 1 | 1 | 0% | 2,450 | 3,589 | +46% | 0 | 0 | — |
case-13 | fail→pass | 5,081 | 1,520 | -70% | 1 | 1 | 0% | 732 | 2,665 | +264% | 0 | 0 | — |
case-14 | fail→pass | 11,414 | 3,243 | -72% | 1 | 1 | 0% | 1,751 | 3,028 | +73% | 0 | 0 | — |
case-15 | fail→pass | 18,087 | 8,623 | -52% | 1 | 1 | 0% | 3,583 | 4,152 | +16% | 0 | 0 | — |
case-16 | pass→pass | 9,890 | 6,877 | -30% | 1 | 1 | 0% | 1,596 | 3,604 | +126% | 0 | 0 | — |
case-17 | pass→pass | 11,827 | 6,771 | -43% | 1 | 1 | 0% | 2,241 | 3,652 | +63% | 0 | 0 | — |
case-18 | pass→pass | 3,961 | 2,839 | -28% | 1 | 1 | 0% | 744 | 2,906 | +291% | 0 | 0 | — |
case-19 | pass→pass | 5,051 | 2,905 | -42% | 1 | 1 | 0% | 794 | 2,981 | +275% | 0 | 0 | — |
case-21 | pass→pass | 11,604 | 4,149 | -64% | 1 | 1 | 0% | 1,847 | 3,016 | +63% | 0 | 0 | — |
case-22 | pass→pass | 10,368 | 5,480 | -47% | 1 | 1 | 0% | 1,783 | 3,319 | +86% | 0 | 0 | — |
case-23 | pass→pass | 12,868 | 13,628 | +6% | 1 | 1 | 0% | 2,585 | 4,973 | +92% | 0 | 0 | — |
case-24 | pass→pass | 13,088 | 7,922 | -39% | 1 | 1 | 0% | 2,464 | 3,968 | +61% | 0 | 0 | — |
case-25 | fail→pass | 15,412 | 4,972 | -68% | 1 | 1 | 0% | 2,491 | 3,271 | +31% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +44 percentage points is the difference between those two pass rates over the 25 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.