kramme-cc-workflow/skills/kramme:code:frontend-authoring/SKILL.md
Build production-quality UI components with sound architecture, accessibility (WCAG 2.1 AA), and anti-AI-aesthetic defaults. Use when creating or modifying user-facing components. Covers component colocation, composition over configuration, state-management tier selection, accessibility patterns, and concrete replacements for AI-aesthetic defaults (purple gradients, rounded-2xl everywhere, oversized padding, shadow-heavy cards).
npx skillsauth add abildtoft/kramme-cc-workflow kramme:code:frontend-authoringInstall 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.
Build production-quality user interfaces that look engineer-crafted, not AI-generated. This is the author-time discipline that keeps "AI defaults" (purple gradients, rounded-2xl on every surface, oversized padding, stock card grids, shadow-heavy layouts) from being baked in and then flagged by review later. The goal: real design-system adherence, real accessibility, real interaction patterns — from the first draft.
Each user-facing change is one pass through this sequence:
SIMPLICITY CHECK marker (see below) stating the simplest UI that satisfies the requirement.<Thing config={...} /> props.references/ai-aesthetic-table.md against your draft.references/accessibility-checklist.md against the component.When you notice something out-of-scope (an adjacent component that's already using a bad default, a typo in a sibling file), emit NOTICED BUT NOT TOUCHING and move on. Do not silently fix adjacent code.
SIMPLICITY CHECK: <one-line summary of the simplest UI that satisfies the requirement>
If the thing you end up building is not the simplest version, add a second line explaining what forced the expansion.
NOTICED BUT NOT TOUCHING: <what you saw>
Why skipping: <out-of-scope / unrelated / deferred>
Keep everything for one component together:
src/components/
TaskList/
TaskList.tsx # Component implementation
TaskList.test.tsx # Tests
TaskList.stories.tsx # Storybook stories (if using)
use-task-list.ts # Custom hook (if complex state)
types.ts # Component-specific types (if needed)
Prefer small components that compose over one large component with many config props.
// Good: composable
<Card>
<CardHeader>
<CardTitle>Tasks</CardTitle>
</CardHeader>
<CardBody>
<TaskList tasks={tasks} />
</CardBody>
</Card>
// Avoid: over-configured
<Card
title="Tasks"
headerVariant="large"
bodyPadding="md"
content={<TaskList tasks={tasks} />}
/>
One component, one responsibility. If a component renders both a filter bar and a list and a pagination control, split it.
// Good: does one thing
export function TaskItem({ task, onToggle, onDelete }: TaskItemProps) {
return (
<li className="flex items-center gap-3 p-3">
<Checkbox checked={task.done} onChange={() => onToggle(task.id)} />
<span className={task.done ? "line-through text-muted" : ""}>
{task.title}
</span>
<Button variant="ghost" size="sm" onClick={() => onDelete(task.id)}>
<TrashIcon />
</Button>
</li>
);
}
Containers handle data shape, loading, error, empty. Presentational components take already-resolved props.
// Container: handles data
export function TaskListContainer() {
const { tasks, isLoading, error, refetch } = useTasks();
if (isLoading) return <TaskListSkeleton />;
if (error)
return <ErrorState message="Failed to load tasks" retry={refetch} />;
if (tasks.length === 0) return <EmptyState message="No tasks yet" />;
return <TaskList tasks={tasks} />;
}
// Presentation: handles rendering
export function TaskList({ tasks }: { tasks: Task[] }) {
return (
<ul role="list" className="divide-y">
{tasks.map((task) => (
<TaskItem key={task.id} task={task} />
))}
</ul>
);
}
Pick the simplest tier that satisfies the requirement. Only move up when the current tier can't express the need.
Local state (useState) → Component-specific UI state
Lifted state → Shared between 2-3 sibling components
Context → Theme, auth, locale (read-heavy, write-rare)
URL state (searchParams) → Filters, pagination, shareable UI state
Server state (React Query, SWR) → Remote data with caching
Global store (Zustand, Redux) → Complex client state shared app-wide
Avoid prop drilling deeper than 3 levels. If you are passing props through components that don't use them, introduce context or restructure the tree.
The single highest-leverage author-time check. Every UI draft must be reviewed against the 9-row anti-pattern table before it ships.
Read references/ai-aesthetic-table.md and confirm none of the 9 anti-patterns describe your draft. If one does, apply the production alternative in the same row before moving on.
Typical catches at this stage:
rounded-2xl to every card, button, and input.Use the project's spacing scale. Do not invent off-scale values.
/* Use the scale: 0.25rem increments (or whatever the project uses) */
/* Good */
padding: 1rem; /* 16px */
/* Good */
gap: 0.75rem; /* 12px */
/* Bad */
padding: 13px; /* Not on any scale */
/* Bad */
margin-top: 2.3rem; /* Not on any scale */
Respect the type hierarchy — don't skip levels, don't use heading styles for non-heading content.
h1 → Page title (one per page)
h2 → Section title
h3 → Subsection title
body → Default text
small → Secondary / helper text
text-primary, bg-surface, border-default), not raw hex values.Every component must meet the standards in references/accessibility-checklist.md. That file inlines the full checklist (keyboard, ARIA, focus, forms, images, motion) — do not defer to other skills or external docs.
The five load-bearing defaults:
<button> over <div role="button">. If you must use a custom element, implement Enter + Space + focus ring.aria-label="Close dialog" on an icon-only button).See the full checklist in references/accessibility-checklist.md before marking the pass done.
Design mobile-first, then expand. Test at the four canonical breakpoints: 320px, 768px, 1024px, 1440px.
<div className="
grid grid-cols-1 /* Mobile: single column */
sm:grid-cols-2 /* Small: 2 columns */
lg:grid-cols-3 /* Large: 3 columns */
gap-4
">
At each breakpoint: does the layout reflow without horizontal scroll, overflow, or clipped content? If not, fix before moving on.
function TaskListSkeleton() {
return (
<div className="space-y-3" aria-busy="true" aria-label="Loading tasks">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="h-12 bg-muted animate-pulse rounded" />
))}
</div>
);
}
When a mutation has a clearly predictable outcome (toggle, like, delete), apply the change immediately and roll back on error.
function useToggleTask() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: toggleTask,
onMutate: async (taskId) => {
await queryClient.cancelQueries({ queryKey: ["tasks"] });
const previous = queryClient.getQueryData(["tasks"]);
queryClient.setQueryData(["tasks"], (old: Task[]) =>
old.map((t) => (t.id === taskId ? { ...t, done: !t.done } : t)),
);
return { previous };
},
onError: (_err, _taskId, context) => {
queryClient.setQueryData(["tasks"], context?.previous);
},
});
}
Before marking a UI slice done, confirm every box:
SIMPLICITY CHECK emitted and honored.NOTICED BUT NOT TOUCHING observations silently fixed.If any box is unchecked, the slice is not done. Fix the gap or split the slice.
kramme:siw:generate-phases) where a phase involves UI work.kramme:code:incremental — each UI slice obeys the six-rule increment loop; this skill adds the UI-specific passes on top.kramme:code:source-driven when the UI slice depends on framework-specific APIs, component-library behavior, or recently changed browser/runtime semantics. This skill owns UI structure, accessibility, and visual defaults; source-driven owns official-doc grounding and citation.kramme:pr:ux-review, the kramme:a11y-auditor agent, and the kramme:deslop-reviewer agent all check, post-hoc, for violations of the same defaults this skill prevents. If a reviewer flags one of the 9 AI-aesthetic patterns, the canonical fix lives in this skill's anti-pattern table.These are the lies you will tell yourself to justify shipping AI-default UI. Each one has a correct response:
rounded-2xl won't hurt." → Consistent radius per surface tier is the point. Pick intentionally per tier.If you notice any of these in your draft, stop and re-author:
any typing on props or event handlers.Before declaring the UI slice done, self-check:
kramme:pr:ux-review or the kramme:deslop-reviewer agent flag any row of the anti-AI-aesthetic table?If any answer is no, finish the gap before declaring done.
development
Compare an existing PR's title and body against the actual branch diff and report drift — false claims, missing major changes, stale scope, missing risk callouts. Use after pushing changes to a branch with an open PR, or before requesting review. Read-only by default; add --fix to delegate to kramme:pr:generate-description for an updated description. Complements kramme:pr:code-review (which checks description accuracy as one signal among many code-quality checks) by being a fast, focused, single-purpose check that runs in seconds.
tools
Reviews plugin skills for focused scope, progressive disclosure, portability, safety, retry behavior, and documentation quality. Use when auditing a SKILL.md, skill directory, or proposed skill text against skill-authoring standards. Not for creating new skills, editing skills, or reviewing ordinary application code.
tools
Reviews recent agent session transcripts to find repeated manual workflows or repeated user asks, then proposes and optionally scaffolds only useful new skills or custom subagents. Use when the user asks to inspect recent sessions, find automation opportunities, or create reusable workflows from repeated work. Not for summarizing one session, general retrospectives, or codebase refactoring.
data-ai
Remove all DONE issues and renumber remaining issues within each prefix group. Not for editing live issue content, archiving still-open issues, or moving issues between prefix groups.