Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tomevault-io/skills-registry --skill frontend명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | frontend |
| description | | Use when this capability is needed. |
This skill provides frontend implementation expertise. Automatically applies design principles from the ux-design skill.
Prerequisites: Load ux-design skill for design principles reference.
components/
├── ui/ # Reusable UI components (Button, Input, Card)
├── features/ # Feature-specific components
├── layouts/ # Page layouts (Header, Footer, Sidebar)
└── pages/ # Full pages
// components/ui/Button.tsx
import { forwardRef } from 'react';
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'tertiary';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
disabled?: boolean;
children: React.ReactNode;
onClick?: () => void;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = 'primary', size = 'md', loading, disabled, children, onClick }, ref) => {
return (
<button
ref={ref}
className={`btn btn-${variant} btn-${size}`}
disabled={disabled || loading}
onClick={onClick}
// Design Principle: Fitts's Law (44px min touch target)
style={{ minHeight: '44px', minWidth: '44px' }}
>
{loading ? <Spinner /> : children}
</button>
);
}
);
// Interactive elements ≥44px
const styles = {
button: { minHeight: '44px', minWidth: '44px', padding: '12px 24px' },
input: { height: '44px', padding: '12px' },
checkbox: { width: '24px', height: '24px' }, // with 44px touch area
};
// ≤7 options visible
function Navigation() {
const primaryItems = items.slice(0, 5); // Limit to 5
const moreItems = items.slice(5); // Rest under "More"
return (
<nav>
{primaryItems.map(item => <NavItem key={item.id} {...item} />)}
{moreItems.length > 0 && <MoreMenu items={moreItems} />}
</nav>
);
}
// Chunk information
function Form() {
return (
<>
{/* Personal Info - 3 fields */}
<fieldset>
<legend>Personal Information</legend>
<Input label="Name" />
<Input label="Email" />
<Input label="Phone" />
</fieldset>
{/* Address - 4 fields */}
<fieldset>
<legend>Address</legend>
<Input label="Street" />
<Input label="City" />
<Input label="State" />
<Input label="ZIP" />
</fieldset>
</>
);
}
// <400ms feedback
function SaveButton() {
const [saving, setSaving] = useState(false);
const handleSave = async () => {
setSaving(true); // Instant feedback
// Optimistic update
updateLocalState(data);
try {
await api.save(data);
} catch (error) {
// Rollback on error
revertLocalState();
} finally {
setSaving(false);
}
};
return (
<Button loading={saving} onClick={handleSave}>
{saving ? 'Saving...' : 'Save'}
</Button>
);
}
/* Base (Mobile) */
.container {
padding: 16px;
display: flex;
flex-direction: column;
}
/* Tablet (≥640px) */
@media (min-width: 640px) {
.container {
padding: 24px;
}
}
/* Desktop (≥1024px) */
@media (min-width: 1024px) {
.container {
padding: 32px;
flex-direction: row;
max-width: 1200px;
margin: 0 auto;
}
}
<div className="
flex flex-col /* Mobile: stack */
md:flex-row /* Tablet: side-by-side */
lg:max-w-screen-xl /* Desktop: max width */
px-4 md:px-6 lg:px-8 /* Responsive padding */
">
{/* Content */}
</div>
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<Button onClick={() => setCount(count + 1)}>Increment</Button>
</div>
);
}
// store/useStore.ts
import { create } from 'zustand';
interface StoreState {
user: User | null;
setUser: (user: User) => void;
}
export const useStore = create<StoreState>((set) => ({
user: null,
setUser: (user) => set({ user }),
}));
// Usage in component
function Profile() {
const user = useStore((state) => state.user);
return <div>{user?.name}</div>;
}
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}
function DataList() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchData()
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, []);
if (loading) return <Skeleton />;
if (error) return <Error message={error.message} />;
return <List items={data} />;
}
<button
aria-label="Close dialog"
aria-pressed={isPressed}
aria-disabled={isDisabled}
>
<Icon name="close" aria-hidden="true" />
</button>
function Dialog({ isOpen, onClose }) {
const dialogRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isOpen) return;
// Trap focus inside dialog
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
// Trap focus logic here
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [isOpen, onClose]);
return isOpen ? (
<div ref={dialogRef} role="dialog" aria-modal="true">
{/* Content */}
</div>
) : null;
}
// Auto-focus first input in form
function LoginForm() {
const emailRef = useRef<HTMLInputElement>(null);
useEffect(() => {
emailRef.current?.focus();
}, []);
return (
<form>
<Input ref={emailRef} label="Email" />
<Input label="Password" type="password" />
<Button type="submit">Login</Button>
</form>
);
}
// Lazy load heavy components
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<Spinner />}>
<HeavyComponent />
</Suspense>
);
}
import { memo, useMemo, useCallback } from 'react';
// Memo component (re-render only if props change)
const ExpensiveComponent = memo(({ data }) => {
// Expensive render logic
return <div>{data}</div>;
});
// Memo value
function DataProcessor({ items }) {
const processed = useMemo(
() => items.map(expensiveProcess),
[items]
);
return <List items={processed} />;
}
// Memo callback
function Parent() {
const handleClick = useCallback(() => {
// Handle click
}, []); // Dependencies
return <Child onClick={handleClick} />;
}
import styles from './Button.module.css';
export function Button({ children }) {
return <button className={styles.button}>{children}</button>;
}
<button className="
min-h-[44px] px-6 py-3
bg-blue-600 hover:bg-blue-700
text-white font-semibold rounded-lg
transition-colors duration-200
disabled:opacity-50 disabled:cursor-not-allowed
">
Click Me
</button>
import styled from 'styled-components';
const Button = styled.button`
min-height: 44px;
padding: 12px 24px;
background: ${props => props.theme.colors.primary};
color: white;
border: none;
border-radius: 8px;
transition: all 200ms ease-out;
&:hover {
background: ${props => props.theme.colors.primaryDark};
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('renders with text', () => {
render(<Button>Click Me</Button>);
expect(screen.getByText('Click Me')).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click</Button>);
fireEvent.click(screen.getByText('Click'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('shows loading state', () => {
render(<Button loading>Click</Button>);
expect(screen.()).();
});
});
Before completing frontend work, verify:
Load ux-design for design principles reference. Apply them automatically during implementation.
Coordinate API contract:
Write component tests for:
Follow code standards:
Converted and distributed by TomeVault — claim your Tome and manage your conversions.