skills/general/ui-developer/SKILL.md
Frontend UI development for Beam Studio V2 with Tailwind and design tokens. Load when user needs Beam Studio UI, components, or Tailwind layout work.
npx skillsauth add beam-ai-team/beam-next-skills ui-developerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are a frontend developer specialized in UI development for Beam Studio V2. Follow these rules strictly.
style={{}})cn() utility from @/lib/utils for conditional class merging// ✅ Correct
<div className={cn('flex items-center gap-2', isActive && 'bg-primary')}>
// ❌ Wrong
<div style={{ display: 'flex', alignItems: 'center' }}>
Use ONLY colors defined in tailwind.config.ts. Never use arbitrary colors.
Allowed Semantic Colors:
primary, primary-foreground
secondary, secondary-foreground
destructive, destructive-foreground
success, success-foreground
warning, warning-foreground
muted, muted-foreground
accent, accent-foreground
background, foreground
border, input, ring
Allowed Color Variants:
primary-100, primary-90, primary-80, primary-50, primary-20, primary-10
muted-100, muted-90, muted-80, muted-50, muted-40, muted-20, muted-10
// ✅ Correct
<div className="bg-primary text-primary-foreground">
<span className="text-muted-foreground">
<div className="border-border bg-muted-40">
// ❌ Wrong
<div className="bg-blue-500">
<span className="text-[#333333]">
<div style={{ backgroundColor: 'red' }}>
src/components/
├── ui/ # Base shadcn components - NEVER MODIFY
├── wrapper/ # Enhanced components with business logic
├── shared/ # Reusable cross-feature components
└── sections/ # Feature-specific components
UI Layer (/components/ui/)
Wrapper Layer (/components/wrapper/)
ComponentName.tsx, ComponentName.stories.tsx, index.tsShared Layer (/components/shared/)
Sections Layer (/components/sections/)
Need basic UI element?
├── Check /components/ui/ first
├── Found? → DO NOT modify. Create wrapper in /wrapper/
└── Not found? → Check shadcn docs, add via CLI
Need enhanced functionality?
├── Add loading/form/icons → Create in /wrapper/
└── Feature-specific logic → Create in /sections/
Reusable across features?
└── Place in /shared/
ComponentName/
├── ComponentName.tsx # Main implementation
├── ComponentName.stories.tsx # Storybook stories
└── index.ts # Default export
import React, { forwardRef } from 'react';
import { cn } from '@/lib/utils';
interface ComponentNameProps {
children: React.ReactNode;
className?: string;
// ... other props
}
/**
* Brief description of what this component does.
* @param children - Child elements to render
* @param className - Additional CSS classes
*/
const ComponentName = forwardRef<HTMLDivElement, ComponentNameProps>(
({ children, className, ...props }, ref) => {
return (
<div ref={ref} className={cn('base-classes', className)} {...props}>
{children}
</div>
);
}
);
ComponentName.displayName = 'ComponentName';
export default ComponentName;
import React, { forwardRef } from 'react';
import { BaseComponent } from '@/components/ui/base-component';
import { cn } from '@/lib/utils';
interface WrapperProps extends BaseComponentProps {
isLoading?: boolean;
LeadingIcon?: React.ReactNode;
TrailingIcon?: React.ReactNode;
}
/**
* Enhanced BaseComponent with loading state and icon support.
* @param isLoading - Shows loading animation when true
* @param LeadingIcon - Icon displayed before content
* @param TrailingIcon - Icon displayed after content
*/
const ComponentWrapper = forwardRef<HTMLElement, WrapperProps>(
({ isLoading = false, LeadingIcon, TrailingIcon, className, ...props }, ref) => {
if (isLoading) {
return <LoadingState />;
}
return (
<div className="relative">
{LeadingIcon && <span className="absolute left-3">{LeadingIcon}</span>}
<BaseComponent
ref={ref}
className={cn(LeadingIcon && 'pl-8', TrailingIcon && 'pr-8', className)}
{...props}
/>
{TrailingIcon && <span className="absolute right-3">{TrailingIcon}</span>}
</div>
);
}
);
export default ComponentWrapper;
Each component should do ONE thing well.
// ✅ Correct - separate concerns
const UserAvatar = ({ src, name }) => (
<Avatar src={src} alt={name} />
);
const UserName = ({ name }) => (
<span className="font-medium">{name}</span>
);
const UserCard = ({ user }) => (
<div className="flex items-center gap-2">
<UserAvatar src={user.avatar} name={user.name} />
<UserName name={user.name} />
</div>
);
// ❌ Wrong - doing too much
const UserCard = ({ user, onEdit, onDelete, showActions, isLoading, error }) => {
// 200+ lines of mixed concerns
};
Before creating a component:
/components/shared/ for existing components/components/wrapper/ for base wrappers// ✅ Reuse existing
import { Card } from '@/components/shared/Card';
import { EmptyState } from '@/components/shared/EmptyState';
// ❌ Don't recreate
const MyCustomCard = () => { /* same as Card */ };
tailwind.config.ts unless explicitly asked/components/ui/ files// ✅ Correct - use existing variant
<Button variant="destructive" size="sm">Delete</Button>
// ❌ Wrong - creating new component for styling
const RedSmallButton = ({ children }) => (
<button className="bg-red-500 text-sm">{children}</button>
);
Maximum 500 lines per component file.
If exceeding, break into smaller components:
// ✅ Correct - split into parts
// AgentDashboard/AgentHeader.tsx
// AgentDashboard/AgentMetrics.tsx
// AgentDashboard/AgentActions.tsx
// AgentDashboard/AgentDashboard.tsx (composes above)
// ❌ Wrong - 800 line monolith
// AgentDashboard.tsx with everything inline
Components used in 2+ places go in /components/shared/.
src/components/shared/
├── Card/
├── EmptyState/
├── Dot/
├── LogoImage/
├── StatusIcon/
└── [YourComponent]/
Every component/page needs these states:
interface ComponentProps {
data: Data[];
isLoading: boolean;
error: Error | null;
}
const MyComponent = ({ data, isLoading, error }: ComponentProps) => {
// Loading State
if (isLoading) {
return <Skeleton className="h-32 w-full" />;
}
// Error State
if (error) {
return (
<div className="text-destructive">
<AlertCircle className="h-4 w-4" />
<span>{error.message}</span>
</div>
);
}
// Empty State
if (data.length === 0) {
return (
<EmptyState
text="No items found"
content="Create your first item to get started"
/>
);
}
// Success State with hover states in JSX
return (
<div className="group hover:bg-muted-40 transition-colors">
{/* Content */}
</div>
);
};
Prioritize readable JSX over clever logic.
// ✅ Correct - clean JSX
const getStatusColor = (status: string): string => {
const colors: Record<string, string> = {
active: 'text-success',
pending: 'text-warning',
failed: 'text-destructive'
};
return colors[status] ?? 'text-muted-foreground';
};
return (
<span className={getStatusColor(status)}>
{status}
</span>
);
// ❌ Wrong - inline logic mess
return (
<span className={status === 'active' ? 'text-success' : status === 'pending' ? 'text-warning' : status === 'failed' ? 'text-destructive' : 'text-muted-foreground'}>
{status}
</span>
);
Always mobile-first, always responsive.
xs: 375px
sm: 375px-768px
md: 768px-1024px
lg: 1024px-1280px
xl: 1280px-1400px
2xl: 1400px+
// Mobile-first responsive
<div className="
flex flex-col sm:flex-row
gap-2 sm:gap-4 md:gap-6
p-4 sm:p-6 md:p-8
w-full sm:w-1/2 md:w-1/3 lg:w-1/4
">
// Use hook for JS logic
const { xs, sm, md } = useScreenSize();
return xs ? <MobileView /> : <DesktopView />;
// ✅ Correct - vertical spacing
<div className="space-y-6">
<Section1 />
<Section2 />
</div>
// ❌ Avoid - border separation
<div className="border-b pb-4 mb-4">
<Section1 />
</div>
// ✅ Correct - constrained width
<div className="mx-auto max-w-4xl px-4">
<Content />
</div>
// ❌ Avoid - full width on desktop
<div className="w-full">
<Content />
</div>
// ✅ Correct - prevent overflow
<p className="max-w-prose truncate">Long text...</p>
<h2 className="line-clamp-2">Multi-line title that may be very long...</h2>
// ✅ Correct - subtle shadow
<div className="rounded-lg bg-background shadow-sm">
// ❌ Avoid - hard outline
<div className="rounded-lg border-2 border-border">
NEVER write API integrations. Use hardcoded JSON data.
// src/mocks/agents.json
{
"agents": [
{ "id": "1", "name": "Agent 1", "status": "active" },
{ "id": "2", "name": "Agent 2", "status": "pending" }
]
}
// Component
import mockAgents from '@/mocks/agents.json';
const AgentList = () => {
const agents = mockAgents.agents;
return <List items={agents} />;
};
Extract logic into named functions.
// ✅ Correct
const handleCardClick = (id: string) => {
setSelectedId(id);
trackAnalytics('card_click', { id });
};
const formatDate = (date: string): string => {
return new Date(date).toLocaleDateString();
};
return (
<Card onClick={() => handleCardClick(item.id)}>
<span>{formatDate(item.createdAt)}</span>
</Card>
);
// ❌ Wrong
return (
<Card onClick={() => {
setSelectedId(item.id);
trackAnalytics('card_click', { id: item.id });
}}>
<span>{new Date(item.createdAt).toLocaleDateString()}</span>
</Card>
);
Create constants for repeated values.
// src/lib/constants.ts
export const STATUS = {
ACTIVE: 'active',
PENDING: 'pending',
FAILED: 'failed'
} as const;
export const STATUS_COLORS: Record<string, string> = {
[STATUS.ACTIVE]: 'text-success',
[STATUS.PENDING]: 'text-warning',
[STATUS.FAILED]: 'text-destructive'
};
export enum ViewMode {
GRID = 'grid',
LIST = 'list'
}
// Usage
import { STATUS, STATUS_COLORS, ViewMode } from '@/lib/constants';
NEVER install new packages unless explicitly requested.
Use existing:
lucide-react for icons@radix-ui/* for primitivesclass-variance-authority for variantsclsx + tailwind-merge via cn()// ✅ Use existing
import { CheckIcon, XIcon } from 'lucide-react';
// ❌ Don't add
import { FaCheck } from 'react-icons/fa'; // NO!
Add JSDoc to all components and functions.
/**
* Displays a status badge with appropriate color coding.
* @param status - Current status value ('active' | 'pending' | 'failed')
* @param showIcon - Whether to display status icon (default: true)
* @returns Styled badge element
* @example
* <StatusBadge status="active" />
* <StatusBadge status="failed" showIcon={false} />
*/
const StatusBadge = ({ status, showIcon = true }: StatusBadgeProps) => {
// implementation
};
/**
* Formats a timestamp into human-readable relative time.
* @param timestamp - ISO date string
* @returns Formatted string like "2 hours ago"
*/
const formatRelativeTime = (timestamp: string): string => {
// implementation
};
<Button isLoading={isSubmitting}>Submit</Button>
<Input
name="email"
label="Email Address"
LeadingIcon={<MailIcon className="h-4 w-4" />}
/>
<Card id={item.id} onSelect={handleSelect}>
{({ isSelected }) => (
<>
<Card.Header>
<Card.IconGroup icons={item.icons} />
<Card.MoreButton options={menuOptions} />
</Card.Header>
<Card.TextContent title={item.name} description={item.description} />
<Card.Footer>
<Card.BadgeGroup badges={item.tags} />
</Card.Footer>
</>
)}
</Card>
<EmptyState
text="No results found"
content={<Button onClick={handleCreate}>Create New</Button>}
/>
<Modal
open={isOpen}
onOpenChange={setIsOpen}
title="Confirm Action"
content={<ModalContent />}
footer={<ModalActions />}
size="md"
/>
testing
Audit registry.yaml against disk, validate SKILL.md frontmatter, find duplicates and orphans. Load when user says 'audit skills registry', 'validate beam-next-skills', 'registry drift', 'skills catalog audit', 'check registry yaml'.
tools
All Workable ATS operations — fetch JDs, search candidates, post assessments/reviews. Load when user says "fetch JD", "search workable", "push to workable", "post review", "rate candidate", "workable", "push assessment", "list jobs", or after interview-coach completes an evaluation. Replaces workable-fetch-jd and workable-push-assessment.
data-ai
Load when user mentions "tavily research", "market intelligence", "competitive research", "GTM research", or needs real-time market data for sales, marketing, or vertical strategy.
development
Shared resource library for Slack integration skills. DO NOT load directly - provides common references (setup, API docs, error handling, authentication) and scripts used by slack-connect and individual Slack skills.