| name | ui-css-patterns |
| description | UI/CSS design patterns reference for RAPID frontend - color semantics, component selection, common layouts |
Frontend UI/CSS Design Patterns
Quick reference for maintaining visual consistency across the RAPID frontend. Use this when implementing UI components or choosing styling approaches.
Related Skills:
- For backend/frontend planning → use
/plan-backend-frontend - For agent prompts → use
/modify-agent-prompts
Core Design Principles
Action Hierarchy & Color Semantics
Decision Matrix (based on 200+ actual usage instances):
| Action Type | Button Variant | Outline | When to Use | Examples |
|---|---|---|---|---|
| Submit/Create | primary |
false |
Main form submission, creating resources | "Create", "Save", "Compare" |
| Cancel/Back | primary |
true |
Dismissing modals, going back | "Cancel", "Back" |
| Delete/Remove | danger |
false (strong)true (soft) |
Destructive actions | "Delete Checklist" (filled) Inline delete (outline) |
| Alternative | secondary |
false |
Non-primary actions that aren't cancel | "Duplicate", "Retry" |
| UI Toggle | text |
n/a | Show/hide, expand/collapse | "Show More", "Expand Tree" |
| Inline Action | text + size="sm" |
n/a | Actions within tree/list items | Edit/Delete in tree nodes |
| Table Action | varies | true |
Actions in data tables | "View", "Download" |
Key Pattern: Outlined primary = cancel/dismiss, Filled primary = submit/create
Color Mappings:
- Primary actions:
aws-sea-blue-light/aws-sea-blue-dark - Secondary actions:
aws-aqua - Success states:
aws-lab(brand green),green-500(icons),green-100/800(badges) - Danger/Error:
red(buttons),red-500(icons),red-100/800(badges) - Warning:
yellow-400(borders),yellow-100/800(badges) - Info/Neutral:
aws-font-color-blue(links),blue-100/800(badges)
How to Determine Color Semantics
5-Step Decision Process:
Identify Action Context
- Completing a task → Primary
- Abandoning a task → Cancel (outlined primary)
- Destroying data → Danger
- Viewing information → Secondary or text
- Toggling UI state → Text
Assess Visual Hierarchy
- One main action + others → Main = filled primary, others = outlined or secondary
- Multiple equal actions → All outlined or secondary
- Inline/subtle actions → Text variant + small size
Consider Consequences
- Irreversible destruction → Danger (red)
- Data mutation → Primary (blue)
- Reversible action → Secondary (aqua) or text
- No side effects → Text
Follow the Pattern - Check similar features:
grep -r "variant=" frontend/src/features/{similar-feature}/Observed patterns:
- Form submissions: 100% filled primary (11/11)
- Modal cancels: 100% outlined primary (16/16)
- Delete operations: Consistent danger variant
- Inline tree actions: 100% text + small
Validate with Questions
- "Is this the ONE thing users came here to do?" → Filled primary
- "Could user want to cancel?" → Add outlined cancel button
- "Will this delete user data?" → Use danger variant
- "Is this a secondary workflow?" → Use secondary or outlined
- "Is this just UI manipulation?" → Use text variant
Anti-Patterns:
- ❌ Multiple filled primary buttons in same view
- ❌ Filled danger for reversible actions
- ❌ Primary for cancel/dismiss
- ❌ Text variant for important mutations
Status & Feedback Colors
For Operation Feedback (Toasts, Alerts):
- Success → Green:
addToast(message, "success") - Error → Red:
addToast(message, "error")or<ErrorAlert> - Warning → Yellow:
<InfoAlert variant="warning"> - Info → Blue:
<InfoAlert variant="info">
For Status Badges:
| State | Color | When to Use |
|---|---|---|
PENDING |
Yellow | Task queued, waiting to start |
PROCESSING |
Blue | Task currently running |
DETECTING |
Purple | AI-specific subprocess |
COMPLETED |
Green | Task finished successfully |
FAILED |
Red | Task encountered error |
For Review Result Badges:
| Result | Color | Meaning |
|---|---|---|
UNDER_REVIEW |
Blue | AI flagged for human review |
PASS |
Green | Item approved/compliant |
NOT_PASS |
Red | Item rejected/non-compliant |
LOW_CONFIDENCE |
Yellow | AI uncertain, needs verification |
USER_OVERRIDE |
AWS Blue | Human decision overrode AI |
Spacing & Layout
Spacing System (8px base unit):
- Common values:
gap-2,gap-4,gap-6,p-4,p-6,mb-8 - Page containers:
max-w-7xl mx-auto px-4 py-8 - Form spacing:
space-y-6
Typography Hierarchy:
- Page titles:
text-3xl font-bold - Section headings:
text-xlortext-2xl - Body text:
text-smortext-base - Metadata:
text-xs text-aws-font-color-gray
Dark Mode Strategy:
- Always pair backgrounds:
bg-white dark:bg-aws-squid-ink-dark - Always pair text:
text-aws-font-color-light dark:text-aws-font-color-dark - Consistent hover states:
hover:bg-aws-paper-light dark:hover:bg-aws-ui-color-dark
Component Placement Principles
Logical Grouping - Actions Near Their Context
Rule: Action buttons should be placed NEAR what they affect, not right-aligned from unrelated labels.
Vertical Layout Pattern:
<div>
{/* Label */}
<span className="text-sm font-medium mb-3 block">
{t("filter.label")}
</span>
{/* Content being acted upon */}
<div className="flex flex-wrap gap-2">
{items}
</div>
{/* Action - logically grouped below content */}
<div className="mt-3">
<Button variant="text" size="sm" onClick={handleClear}>
{t("filter.clear")}
</Button>
</div>
</div>
Anti-Pattern - justify-between with conditional action:
// ❌ BAD - action appears far from content it affects
<div className="flex items-center justify-between mb-3">
<span>{t("filter.label")}</span>
{hasSelection && (
<Button onClick={handleClear}>Clear</Button> /* Far from filtered content */
)}
</div>
<div className="flex gap-2">
{items}
</div>
Why It Matters:
- Users look for actions near the content they affect
- Right-aligned buttons that appear suddenly are disorienting
- Vertical flow is more predictable: Label → Content → Action
- Especially important for actions that appear conditionally
Real-World Fix: TagFilter Clear button moved below tags instead of right-aligned next to label.
Component Composition
- Check
src/components/before creating new components - Use existing components as building blocks
- Never use native
<button>- always useButtoncomponent - Never use native
alert()orconfirm()- useuseAlerthook - Always use
react-icons- SVG usage prohibited - Always pair color with icon/text for accessibility
Interactive Element Patterns
Visual Affordance - "Make it Obviously Clickable"
Critical Rule: cursor-pointer alone is NOT enough. Elements must LOOK interactive BEFORE hovering.
Pattern: Use visible borders, distinct backgrounds, or shadows in default state.
Tag/Pill/Chip Pattern:
<button className="
cursor-pointer border-2 transition-all
border-light-gray dark:border-aws-ui-color-dark
hover:border-aws-sea-blue-light dark:hover:border-aws-sea-blue-dark
bg-aws-paper-light dark:bg-aws-paper-dark
hover:bg-light-gray dark:hover:bg-aws-squid-ink-dark
">
Interactive Tag
</button>
Key Indicators of Clickability:
cursor-pointer- Changes cursor on hoverborder-2with visible color - Shows it's an element you can interact withtransition-all- Smooth feedback on interaction- Hover state changes - Border and background color shift
Anti-Pattern:
- ❌ Using only
cursor-pointer+ subtle hover background change - ❌ Making elements look like static labels/badges
- ❌ Relying on hover alone to indicate interactivity
Real-World Fix: TagFilter tags were not obviously clickable. Added border-2 with visible borders in default state, not just on hover.
Smooth Transitions for Conditional UI
Pattern: Always render, control with opacity + height transitions.
<div className={`transition-all duration-300 ${
isVisible
? 'opacity-100 max-h-10'
: 'opacity-0 max-h-0 pointer-events-none'
}`}>
<Component />
</div>
When to Use: Conditionally shown buttons, dropdown menus, expandable sections.
Why: No layout shift, smooth 300ms fade (matches Sidebar).
Component Quick Reference
Discover available components:
ls -la frontend/src/components/
| Use Case | Component | Key Props | Location |
|---|---|---|---|
| Primary/secondary actions | Button |
variant outline size |
/components/Button.tsx |
| Form text input | FormTextField |
label error required |
/components/FormTextField.tsx |
| Form textarea | FormTextArea |
label error rows |
/components/FormTextArea.tsx |
| File upload | FormFileUpload |
onFilesSelected accept |
/components/FormFileUpload.tsx |
| Modals/dialogs | Modal |
isOpen onClose title |
/components/Modal.tsx |
| Alert confirmations | useAlert hook |
showAlert showConfirm AlertModal |
/hooks/useAlert.ts |
| Page layout | Layout |
children |
/components/Layout.tsx |
| Navigation sidebar | Sidebar |
/components/Sidebar.tsx |
|
| Page header | PageHeader |
title description actions |
/components/PageHeader.tsx |
| Data tables | Table |
columns data loading |
/components/Table.tsx |
| Status badges | StatusBadge |
status |
/components/StatusBadge.tsx |
| Result cards | ResultCard |
title content variant |
/components/ResultCard.tsx |
| Loading skeleton | Skeleton |
count height |
/components/Skeleton.tsx |
| Pagination | Pagination |
currentPage totalPages onPageChange |
/components/Pagination.tsx |
| Error display | ErrorAlert |
error title |
/components/ErrorAlert.tsx |
| Info display | InfoAlert |
message variant |
/components/InfoAlert.tsx |
| Breadcrumbs | Breadcrumb |
items |
/components/Breadcrumb.tsx |
| Segmented control | SegmentedControl |
options value onChange |
/components/SegmentedControl.tsx |
| Tooltips | Tooltip |
content position |
/components/Tooltip.tsx |
Pattern: When a new component is added, add one row to this table.
Common Implementation Patterns
Page Layout
<div className="container mx-auto px-4 py-8 max-w-7xl">
{/* Page header */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-aws-font-color-light dark:text-aws-font-color-dark mb-2">
{t("page.title")}
</h1>
<p className="text-aws-font-color-gray">
{t("page.description")}
</p>
</div>
{/* Page content */}
{content}
</div>
With action button:
<div className="mb-6 flex items-center justify-between">
<div>
<div className="flex items-center">
<Icon className="mr-2 h-8 w-8 text-aws-font-color-light dark:text-aws-font-color-dark" />
<h1 className="text-3xl font-bold text-aws-font-color-light dark:text-aws-font-color-dark">
{t("page.title")}
</h1>
</div>
<p className="mt-2 text-aws-font-color-gray">
{t("page.description")}
</p>
</div>
<Button variant="primary">{t("action.create")}</Button>
</div>
Responsive Grid
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{items.map((item) => (
<Card key={item.id} item={item} />
))}
</div>
Form Layout
<form onSubmit={handleSubmit} className="space-y-6">
<FormTextField
label={t("form.name")}
value={name}
onChange={setName}
error={errors.name}
required
/>
<FormTextArea
label={t("form.description")}
value={description}
onChange={setDescription}
rows={4}
/>
<div className="flex justify-end gap-3">
<Button variant="primary" outline onClick={onCancel}>
{t("common.cancel")}
</Button>
<Button variant="primary" type="submit" disabled={isSubmitting}>
{t("common.save")}
</Button>
</div>
</form>
State Handling (Loading, Error, Empty, Success)
{isLoading ? (
<Skeleton count={5} height={80} />
) : error ? (
<ErrorAlert error={error} title={t("error.loadFailed")} />
) : items.length === 0 ? (
<div className="text-center py-12">
<p className="text-aws-font-color-gray text-lg mb-4">
{t("list.emptyState")}
</p>
<Button variant="primary" onClick={handleCreate}>
{t("action.createFirst")}
</Button>
</div>
) : (
<div className="grid grid-cols-1 gap-6">
{items.map(item => <ItemCard key={item.id} item={item} />)}
</div>
)}
Modal Workflow
const [isOpen, setIsOpen] = useState(false);
// In render
<>
<Button variant="primary" onClick={() => setIsOpen(true)}>
{t("action.open")}
</Button>
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title={t("modal.title")}
>
<form onSubmit={handleSubmit}>
{/* Form fields */}
<div className="flex justify-end gap-3 mt-6">
<Button variant="primary" outline onClick={() => setIsOpen(false)}>
{t("common.cancel")}
</Button>
<Button variant="primary" type="submit">
{t("common.save")}
</Button>
</div>
</form>
</Modal>
</>
Toast Notifications
import { useToast } from '@/contexts/ToastContext';
const { addToast } = useToast();
// Success
addToast(t("checklist.createSuccess"), "success");
// Error
addToast(t("checklist.createError"), "error");
// With try/catch
try {
await createChecklistSet({ name, description });
addToast(t("success.message"), "success");
} catch (error) {
addToast(t("error.message"), "error");
}