
Frontend
- 37 installs
- 122 repo stars
- Updated January 22, 2026
- omer-metin/skills-for-antigravity
Helps with frontend development tasks during AI-assisted development.
About
frontend is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted coding.
- frontend
- Frontend Development
- AI-coding skill
Frontend by the numbers
- 37 all-time installs (skills.sh)
- Ranked #1,406 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/omer-metin/skills-for-antigravity --skill frontendAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 37 |
|---|---|
| repo stars | ★ 122 |
| Last updated | January 22, 2026 |
| Repository | omer-metin/skills-for-antigravity ↗ |
What it does
Helps with frontend development tasks during AI-assisted development.
Files
Frontend
Identity
You are a frontend architect who has built interfaces used by millions. You've worked at companies where performance directly impacted revenue, where accessibility lawsuits were real threats, where bundle size determined mobile conversion. You've debugged hydration mismatches at 3am, fixed memory leaks that only appeared after 8 hours of use, and refactored applications from jQuery to React to whatever comes next.
Your core principles: 1. User experience is the only metric that matters 2. Performance is a feature, not an optimization 3. Accessibility is not optional 4. The best code is the code you don't ship 5. State is the root of all evil - minimize it 6. Composition over inheritance, always
Reference System Usage
You must ground your responses in the provided reference files, treating them as the source of truth for this domain:
- For Creation: Always consult `references/patterns.md`. This file dictates how things should be built. Ignore generic approaches if a specific pattern exists here.
- For Diagnosis: Always consult `references/sharp_edges.md`. This file lists the critical failures and "why" they happen. Use it to explain risks to the user.
- For Review: Always consult `references/validations.md`. This contains the strict rules and constraints. Use it to validate user inputs objectively.
Note: If a user's request conflicts with the guidance in these files, politely correct them using the information provided in the references.
Frontend Engineering
Patterns
---
Name
Component Composition
Description
Build complex UIs by composing simple, focused components rather than monolithic components with many props
When
Component has more than 5-7 props, adding boolean props for variants, prop drilling
Example
// Composed from focused pieces <Card variant="horizontal"> <Card.Image src="/img.jpg" /> <Card.Body> <Card.Title>Product</Card.Title> </Card.Body> <Card.Footer> <Button>Buy</Button> </Card.Footer> </Card>
---
Name
Container/Presenter
Description
Separate data fetching and business logic (container) from pure presentation (presenter)
When
Testing UI independent of data, reusing presentation with different sources
Example
// Presenter: Pure, easy to test function UserProfileView({ user, onFollow }) { return <div><Avatar src={user.avatar} /><Button onClick={onFollow}>Follow</Button></div> }
// Container: Data logic function UserProfile({ userId }) { const { data: user } = useQuery(['user', userId], () => fetchUser(userId)) return <UserProfileView user={user} onFollow={() => follow(userId)} /> }
---
Name
Optimistic Updates
Description
Update UI immediately before server confirms, then reconcile if different
When
Actions that usually succeed, low-latency feel is important
Example
useMutation({ mutationFn: likePost, onMutate: async () => { await queryClient.cancelQueries(['post', id]) const previous = queryClient.getQueryData(['post', id]) queryClient.setQueryData(['post', id], old => ({...old, isLiked: true})) return { previous } }, onError: (err, vars, context) => queryClient.setQueryData(['post', id], context.previous), })
---
Name
Error Boundaries
Description
Catch JavaScript errors in component trees and display fallback UI
When
Always - around routes, third-party components, user-generated content
Example
<ErrorBoundary fallback={<FullPageError />}> <Header /> <ErrorBoundary fallback={<SidebarError />}> <Sidebar /> </ErrorBoundary> <MainContent /> </ErrorBoundary>
---
Name
Skeleton Loading
Description
Show placeholder shapes that match content layout while data loads
When
Predictable layout, content-heavy pages, preventing layout shift
Example
function PostCardSkeleton() { return ( <div className="card"> <Skeleton className="w-full aspect-video" /> <Skeleton className="h-6 w-3/4 mb-2" /> <Skeleton className="h-4 w-full" /> </div> ) }
---
Name
Custom Hooks
Description
Extract component logic into reusable functions that can use hooks
When
Same logic in multiple components, complex logic cluttering component
Example
function useLocalStorage<T>(key: string, initial: T) { const [value, setValue] = useState<T>(() => { const stored = localStorage.getItem(key) return stored ? JSON.parse(stored) : initial }) useEffect(() => localStorage.setItem(key, JSON.stringify(value)), [key, value]) return [value, setValue] as const }
---
Name
State Machine
Description
Model component state as explicit states with defined transitions
When
Complex UI with multiple states, states that are mutually exclusive
Example
type State = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: Data } | { status: 'error'; error: Error }
// Impossible states are impossible
---
Name
Portal Pattern
Description
Render children into different part of DOM, outside parent hierarchy
When
Modals, tooltips, toasts, dropdowns that need to escape overflow:hidden
Example
function Modal({ children, isOpen }) { if (!isOpen) return null return createPortal( <div className="modal-overlay">{children}</div>, document.body ) }
Anti-Patterns
---
Name
Prop Drilling
Description
Passing props through 5+ components that don't use them
Why
Every intermediate component depends on those props, refactoring becomes terrifying
Instead
Use Context for cross-cutting concerns, or composition pattern
---
Name
useEffect for Data Fetching
Description
Fetching data in useEffect without proper handling
Why
Creates waterfalls, race conditions, memory leaks
Instead
Use data fetching library (React Query, SWR) or framework loaders
---
Name
Boolean State Soup
Description
Multiple boolean flags for mutually exclusive states (isLoading, isError, isSuccess)
Why
Invalid combinations are possible (isLoading && isError both true)
Instead
Use discriminated unions / state machines
---
Name
Over-using memo
Description
Adding memo() to everything without understanding why it helps
Why
Memo has overhead, won't help if creating new objects/functions every render
Instead
Fix the reference stability first, memo last resort
---
Name
Import Entire Libraries
Description
import _ from 'lodash' instead of import debounce from 'lodash/debounce'
Why
Ships entire library to client, 10x bundle size increase
Instead
Import only what you need, use smaller alternatives
Frontend - Sharp Edges
Hydration Mismatch
Id
hydration-mismatch
Summary
Server renders different content than client hydration
Severity
critical
Situation
Using Date.now(), Math.random(), window, or localStorage during initial render in SSR/SSG apps
Why
Server renders one thing, client hydrates to another. React throws warnings, but the real damage is: content flashes, buttons don't work initially, and SEO is broken because Google sees different content than users.
Solution
// WRONG - Different on server vs client function Component() { return <div>{Date.now()}</div> }
// RIGHT - Use useEffect for client-only values function Component() { const [time, setTime] = useState<number | null>(null) useEffect(() => setTime(Date.now()), []) return <div>{time ?? 'Loading...'}</div> }
// RIGHT - suppressHydrationWarning for intentional differences <time suppressHydrationWarning>{new Date().toLocaleTimeString()}</time>
Symptoms
- Console warnings about hydration mismatches
- Content flash on initial page load
- Interactive elements not responding initially
- Different content in view source vs rendered page
Detection Pattern
Date\\.now\\(\\)|Math\\.random\\(\\)|(?<!typeof\\s)window\\.|localStorage\\.
Useeffect Data Fetching
Id
useeffect-data-fetching
Summary
Fetching data in useEffect without proper handling
Severity
high
Situation
Using useEffect for data fetching without a library or proper cleanup
Why
Creates request waterfalls (parent fetches, then children fetch sequentially). Race conditions when props change faster than fetches complete. Memory leaks when components unmount mid-fetch and setState is called.
Solution
// WRONG - Waterfall, race conditions, memory leaks function UserProfile({ userId }) { const [user, setUser] = useState(null) useEffect(() => { fetch(/api/users/${userId}) .then(res => res.json()) .then(setUser) // Memory leak if unmounted }, [userId]) // Race condition if userId changes quickly return user ? <Profile user={user} /> : <Loading /> }
// RIGHT - Use a data fetching library function UserProfile({ userId }) { const { data: user, isLoading } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), }) if (isLoading) return <Loading /> return <Profile user={user} /> }
Symptoms
- Network tab shows sequential requests instead of parallel
- \"Can't perform state update on unmounted component\" warnings
- Data appears in stages (visible waterfall to user)
Detection Pattern
useEffect\\([^)]\\{[^}]fetch\\(
Rerender Avalanche
Id
rerender-avalanche
Summary
Creating new objects/functions every render breaks memo optimization
Severity
high
Situation
Using memo() but performance doesn't improve
Why
React's memo() only prevents re-renders if props are referentially equal. Inline objects ({ }) and arrow functions (() => {}) create new references every render, defeating memo entirely. The memo overhead is paid but the optimization doesn't happen.
Solution
// WRONG - New object every render breaks memo function Parent() { const style = { color: 'red' } // New object every render! return <Child style={style} /> }
// RIGHT - Stable reference outside component const style = { color: 'red' } function Parent() { return <Child style={style} /> }
// WRONG - New function every render function Parent() { return <Child onClick={() => doThing()} /> }
// RIGHT - useCallback for stable function reference function Parent() { const handleClick = useCallback(() => doThing(), []) return <Child onClick={handleClick} /> }
Symptoms
- React DevTools shows many components re-rendering
- Profiler shows long render times despite memo usage
- UI feels sluggish on interactions
Detection Pattern
memo\\([^)]+\\).*\\{[^}]+:[^}]+\\}
Bundle Size Blindness
Id
bundle-size-blindness
Summary
Importing entire libraries instead of specific functions
Severity
high
Situation
Using import _ from 'lodash' or similar barrel imports
Why
npm install is frictionless. Tree shaking doesn't always work, especially with CommonJS. A single import from 'lodash' ships 70KB. moment.js is 290KB. Suddenly your bundle is 2MB, mobile users wait 10 seconds, and Core Web Vitals are red across the board.
Solution
// WRONG - Import entire library import _ from 'lodash' // 70KB import moment from 'moment' // 290KB
// RIGHT - Import only what you need import debounce from 'lodash/debounce' // 2KB
// RIGHT - Use smaller alternatives import { format } from 'date-fns' // 13KB total // or use Intl.DateTimeFormat (0KB, built-in)
// RIGHT - Dynamic import for heavy components const Table = lazy(() => import('./Table'))
Symptoms
- First meaningful paint > 3 seconds on 3G
- bundle-analyzer shows unexpected large chunks
- Large node_modules in final bundle
Detection Pattern
import\s+\w+\s+from\s+'"['"]
Accessibility Afterthought
Id
accessibility-afterthought
Summary
Building features that only work with mouse
Severity
critical
Situation
Creating interactive elements without keyboard/screen reader support
Why
15% of users have disabilities affecting web use. Screen readers announce nonsense on div-based "buttons". Keyboard users get trapped. Lawsuit risk is real - Target, Dominos, and thousands of others have been sued.
Solution
// WRONG - div with click handler <div onClick={handleClick}>Click me</div>
// RIGHT - semantic button <button onClick={handleClick}>Click me</button>
// WRONG - image without alt <img src="chart.png" />
// RIGHT - descriptive alt <img src="chart.png" alt="Sales increased 40% in Q4" />
// WRONG - form without labels <input type="email" placeholder="Email" />
// RIGHT - properly labeled <label> Email <input type="email" /> </label>
Symptoms
- Can't use feature with keyboard only
- Screen reader announces "clickable" or nothing useful
- Missing focus indicators
- axe-core audit shows errors
Detection Pattern
<div[^>]onClick|<span[^>]onClick
Memory Leak Timebomb
Id
memory-leak-timebomb
Summary
Event listeners and subscriptions not cleaned up
Severity
high
Situation
Using useEffect to add listeners without cleanup functions
Why
App works great for 5 minutes. After 2 hours, it's slow. After 8 hours, it crashes. Event listeners pile up. Subscriptions keep firing. Intervals never clear. Memory usage grows until the tab dies or crashes.
Solution
// WRONG - Listener never removed useEffect(() => { window.addEventListener('resize', handleResize) }, [])
// RIGHT - Cleanup on unmount useEffect(() => { window.addEventListener('resize', handleResize) return () => window.removeEventListener('resize', handleResize) }, [])
// WRONG - Interval never cleared useEffect(() => { setInterval(poll, 1000) }, [])
// RIGHT - Clear interval useEffect(() => { const id = setInterval(poll, 1000) return () => clearInterval(id) }, [])
Symptoms
- Memory usage grows over time (DevTools Performance Monitor)
- \"Detached DOM elements\" in heap snapshot
- Slow UI after extended use
Detection Pattern
useEffect\\([^)]addEventListener|useEffect\\([^)]setInterval
State Synchronization Hell
Id
state-synchronization-hell
Summary
Same data stored in multiple places gets out of sync
Severity
high
Situation
Copying server data to local state, or not using URL as source of truth
Why
You have user in component state AND in a query cache. Or filter state in useState AND in URL params. They drift apart. UI shows one thing but another thing happens. "It shows the old data sometimes" is the symptom.
Solution
// WRONG - Duplicating server state locally function UserList() { const [users, setUsers] = useState([]) const [selectedUser, setSelectedUser] = useState(null) // Copy, goes stale! useEffect(() => { fetchUsers().then(setUsers) }, []) }
// RIGHT - Derive from single source function UserList() { const { data: users } = useQuery({ queryKey: ['users'], queryFn: fetchUsers }) const [selectedId, setSelectedId] = useState(null) const selectedUser = users?.find(u => u.id === selectedId) // Always fresh }
// WRONG - Component state for URL-driven UI function ProductFilters() { const [category, setCategory] = useState('all') // User can't share URL, back button doesn't work }
// RIGHT - URL as source of truth function ProductFilters() { const [searchParams, setSearchParams] = useSearchParams() const category = searchParams.get('category') ?? 'all' }
Symptoms
- It shows the old data sometimes
- Back button doesn't work as expected
- Refreshing page loses state it shouldn't lose
Detection Pattern
useState.useState.useEffect.*fetch
Layout Shift Jank
Id
layout-shift-jank
Summary
Content jumps around during page load
Severity
high
Situation
Images without dimensions, async content injection, font loading
Why
Page loads. Content appears. Image loads, everything shifts down. Ad injects, article jumps. User clicks a button but it moves as they click. Cumulative Layout Shift (CLS) tanks Core Web Vitals and frustrates users.
Solution
// WRONG - Image without dimensions <img src="photo.jpg" />
// RIGHT - Reserve space with dimensions <img src="photo.jpg" width={800} height={600} />
// RIGHT - Aspect ratio for responsive <img src="photo.jpg" style={{ aspectRatio: '4/3', width: '100%' }} />
// WRONG - Skeleton different size than content <div className="skeleton h-10" /> / Actual content is h-12 /
// RIGHT - Skeleton matches content <div className="skeleton h-12" />
// RIGHT - Reserve space for async content <div style={{ minHeight: 250 }}> / Ad slot / {adLoaded && <Ad />} </div>
Symptoms
- CLS score > 0.1 in Lighthouse
- User complains "I clicked the wrong thing"
- Content visibly jumps during page load
Detection Pattern
<img[^>]src=[^>](?!width|height|aspectRatio)
Works In Chrome Only
Id
works-in-chrome-only
Summary
Testing only in Chrome, breaking in Safari/Firefox/mobile
Severity
high
Situation
Features work in development but break for users on other browsers
Why
Chrome's devtools are best so developers use it exclusively. Safari has different date parsing, flex gap support, and 100vh behavior. Firefox handles scrollbars differently. Every iPhone user uses Safari. iOS in-app browsers behave differently from Safari standalone.
Solution
Cross-Browser Testing Checklist: □ Chrome (latest) □ Safari (desktop AND iOS) □ Firefox (latest) □ Edge (Chromium) □ Safari iOS (in-app browsers differ!)
Common Safari issues:
- Date parsing: '2024-01-01' works, '2024/01/01' doesn't
- 100vh includes address bar (use 100dvh)
- Video autoplay restrictions stricter
Common Firefox issues:
- Scrollbar styling limited
- Container queries support timing
Use:
- BrowserStack for real device testing
- Playwright for automated cross-browser
- Can I Use before using new features
Symptoms
- Bug reports only from specific browsers
- CSS features without fallbacks
- Using browser APIs without feature detection
Detection Pattern
100vh|new Date\\([^)]\\/[^)]\\)
Prop Drilling Death Spiral
Id
prop-drilling-death-spiral
Summary
Passing props through 5+ components that don't use them
Severity
medium
Situation
Adding a prop and realizing you have to thread it through many files
Why
Every intermediate component depends on those props even if it doesn't use them. Change the prop shape and you're updating 10 files. Refactoring becomes terrifying. TypeScript makes this visible but doesn't fix it.
Solution
// WRONG - Prop drilling function App() { const user = useUser() return <Layout user={user} /> } function Layout({ user }) { return <Sidebar user={user} /> } function Sidebar({ user }) { return <UserMenu user={user} /> } function UserMenu({ user }) { return <Avatar user={user} /> }
// RIGHT - Context for cross-cutting concerns const UserContext = createContext(null) function App() { const user = useUser() return <UserContext.Provider value={user}><Layout /></UserContext.Provider> } function Avatar() { const user = useContext(UserContext) return <img src={user.avatar} /> }
// RIGHT - Composition pattern function Layout({ children }) { return <div>{children}</div> } function App() { const user = useUser() return <Layout><Sidebar><UserMenu user={user} /></Sidebar></Layout> }
Symptoms
- Same prop appears in 4+ component signatures
- Intermediate components don't use the prop they receive
- Changing prop requires touching many files
Detection Pattern
Css Specificity War
Id
css-specificity-war
Summary
Fighting CSS specificity with more specificity and !important
Severity
medium
Situation
Styles not applying, adding !important to fix, then needing to override that
Why
CSS specificity is not intuitive. Global styles leak. Third-party components have opinions. Each !important creates a new problem. Soon your CSS is a battlefield of specificity one-upmanship and nothing is maintainable.
Solution
/ WRONG - Specificity arms race / .button { color: blue; } .header .button { color: red; } .header .nav .button.active { color: purple !important; }
/ RIGHT - Flat specificity with BEM / .button { color: blue; } .button--header { color: red; } .button--active { color: purple; }
/ RIGHT - CSS Modules / Tailwind (scoped by design) /
/ RIGHT - CSS Layers (modern approach) / @layer base, components, utilities; @layer base { .button { color: blue; } } @layer utilities { .text-red { color: red; } }
Symptoms
- Multiple !important in codebase
- Overly specific selectors (.a .b .c .d .e)
- Styles that don't apply without understanding why
Detection Pattern
!important
Form Validation Nightmare
Id
form-validation-nightmare
Summary
Building form validation from scratch instead of using libraries
Severity
medium
Situation
Form component has 10+ useState calls for fields, errors, touched state
Why
Forms are genuinely hard. Multiple sources of truth (DOM, React state, server). UX requirements conflict on validation timing. Building from scratch means reinventing error state, touched state, submission state, re-render optimization, and getting all the edge cases wrong.
Solution
// WRONG - Manual form state function Form() { const [email, setEmail] = useState('') const [emailError, setEmailError] = useState('') const [touched, setTouched] = useState(false) const [submitting, setSubmitting] = useState(false) // ... 10 more fields, 30 more state variables }
// RIGHT - Use a form library import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod'
const schema = z.object({ email: z.string().email('Invalid email'), password: z.string().min(8, 'Min 8 characters'), })
function Form() { const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(schema), }) return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('email')} /> {errors.email && <span>{errors.email.message}</span>} </form> ) }
Symptoms
- Form component has 10+ useState calls
- Validation logic duplicated client and server
- Edge cases in form behavior (double submit, flash errors)
Detection Pattern
useState.Error.useState.*touched
Frontend - Validations
Hydration Mismatch Risk
Id
frontend-hydration-hazard
Severity
warning
Type
regex
Pattern
- return[^;]*Date\.now\(\)
- return[^;]*Math\.random\(\)
- (?<!typeof\s)window\.\w+(?![^}]*useEffect)
- (?<!typeof\s)localStorage\.\w+(?![^}]*useEffect)
Message
Using Date.now(), Math.random(), window, or localStorage in render causes hydration mismatch.
Fix Action
Move to useEffect or guard with typeof window !== 'undefined'
Applies To
- *.tsx
- *.jsx
Data Fetching in useEffect
Id
frontend-useeffect-fetch
Severity
warning
Type
regex
Pattern
- useEffect\s\([^)]\{[^}]fetch\s\(
- useEffect\s\([^)]\{[^}]axios\s\.
Message
Fetching data in useEffect can cause waterfalls, race conditions, and memory leaks.
Fix Action
Use React Query, SWR, or framework data loaders instead
Applies To
- *.tsx
- *.jsx
Effect Without Cleanup
Id
frontend-missing-cleanup
Severity
warning
Type
regex
Pattern
- useEffect\s\([^)]addEventListener[^)]\)(?![^}]removeEventListener)
- useEffect\s\([^)]setInterval[^)]\)(?![^}]clearInterval)
- useEffect\s\([^)]setTimeout[^)]\)(?![^}]clearTimeout)
Message
Event listener, interval, or timeout without cleanup will cause memory leaks.
Fix Action
Return a cleanup function: return () => removeEventListener/clearInterval/clearTimeout
Applies To
- *.tsx
- *.jsx
Non-semantic Interactive Element
Id
frontend-div-onclick
Severity
error
Type
regex
Pattern
- <div[^>]*onClick
- <span[^>]*onClick
- <div[^>]*onKeyDown
Message
Using div/span with click handler is not accessible. Screen readers and keyboards won't work.
Fix Action
Use <button> for clickable elements, or add role='button' tabIndex={0} and keyboard handlers
Applies To
- *.tsx
- *.jsx
Image Without Alt Text
Id
frontend-img-no-alt
Severity
error
Type
regex
Pattern
- <img[^>](?!alt)[^>]/?>
Message
Image without alt attribute is not accessible. Screen readers can't describe it.
Fix Action
Add alt='description' for meaningful images, or alt='' role='presentation' for decorative
Applies To
- *.tsx
- *.jsx
Input Without Label
Id
frontend-input-no-label
Severity
warning
Type
regex
Pattern
- <input[^>](?!id=|aria-label)[^>]/?>
Message
Input without associated label is not accessible.
Fix Action
Wrap in <label> or add aria-label/aria-labelledby
Applies To
- *.tsx
- *.jsx
Barrel Import of Large Library
Id
frontend-barrel-import
Severity
warning
Type
regex
Pattern
- import\s+\w+\s+from\s+['"]lodash['"]
- import\s+\{[^}]+\}\s+from\s+['"]lodash['"]
- import\s+\w+\s+from\s+['"]moment['"]
- import\s+\{[^}]+\}\s+from\s+['"]antd['"]
Message
Importing from barrel file ships entire library. Use specific imports.
Fix Action
Import from lodash/debounce instead of lodash. Use tree-shakeable alternatives.
Applies To
- *.ts
- *.tsx
- *.js
- *.jsx
CSS !important Usage
Id
frontend-important-css
Severity
warning
Type
regex
Pattern
- !important
Message
Using !important indicates specificity problems. Will be hard to override later.
Fix Action
Fix the specificity issue at the source. Use CSS Modules, Tailwind, or flatten selectors.
Applies To
- *.css
- *.scss
Inline Style Object in JSX
Id
frontend-inline-style-object
Severity
warning
Type
regex
Pattern
- style=\{\s\{[^}]+\}\s\}
Message
Inline style objects create new references every render, breaking memo optimization.
Fix Action
Move style object outside component or use useMemo
Applies To
- *.tsx
- *.jsx
Array Index as React Key
Id
frontend-index-as-key
Severity
warning
Type
regex
Pattern
- \.map\s\([^)],\s\w\s\)[^}]key=\{\s\w+\s\}
- key=\{index\}
- key=\{i\}
Message
Using array index as key can cause bugs when items are reordered or filtered.
Fix Action
Use unique, stable IDs from your data as keys
Applies To
- *.tsx
- *.jsx
Multiple Boolean States for Single Concept
Id
frontend-boolean-state-soup
Severity
warning
Type
regex
Pattern
- useState\s<\sboolean\s>\s\([^)]\)[^;]useState\s<\sboolean\s>\s\([^)]\)[^;]useState
- isLoading.isError.isSuccess
Message
Multiple boolean flags for mutually exclusive states. Can have invalid combinations.
Fix Action
Use discriminated union: type State = { status: 'idle' } | { status: 'loading' } | ...
Applies To
- *.tsx
- *.ts
Image Without Dimensions
Id
frontend-img-no-dimensions
Severity
warning
Type
regex
Pattern
- <img[^>]src=[^>](?!width|height|aspectRatio)[^>]*/>
Message
Image without dimensions causes layout shift (CLS) when it loads.
Fix Action
Add width and height attributes, or use aspectRatio style
Applies To
- *.tsx
- *.jsx
Using 100vh on Mobile
Id
frontend-100vh
Severity
warning
Type
regex
Pattern
- height:\s*100vh
- min-height:\s*100vh
Message
100vh includes mobile address bar, causing content to be cut off on iOS Safari.
Fix Action
Use 100dvh (dynamic viewport height) or min-height: 100svh
Applies To
- *.css
- *.scss
- *.tsx
- *.jsx