This guide documents Callora's design tokens, component library, and visual specifications. All contributors must follow these guidelines to ensure consistency across the application.
Always use CSS custom properties (design tokens) instead of hardcoded hex values.
❌ Don't do this:
<div style={{ color: "#4e85ff", background: "#ffffff" }}>✅ Do this:
<div style={{ color: "var(--accent)", background: "var(--surface)" }}>This ensures:
- Consistent theming across light/dark modes
- Easy maintenance and updates
- Accessibility compliance
- Reduced technical debt
All design tokens are defined in src/index.css. They are organized by category and theme.
These tokens are shared across both light and dark themes.
| Token | Value | Purpose |
|---|---|---|
--font-family |
'Space Grotesk', 'Segoe UI', sans-serif |
Primary typeface for all text |
--radius-xl |
28px |
Extra-large border radius (cards, modals) |
--radius-lg |
20px |
Large border radius (sections, panels) |
--radius-md |
16px |
Medium border radius (buttons, inputs) |
--transition-speed |
240ms |
Standard animation duration |
--focus-ring |
0 0 0 3px rgba(78, 133, 255, 0.55) |
Focus indicator for keyboard navigation |
--focus-ring-offset |
0 0 0 5px rgba(78, 133, 255, 0.55) |
Extended focus ring for better visibility |
| Token | Value | Usage |
|---|---|---|
--page-bg |
#0b1020 |
Main page background |
--surface |
rgba(14, 20, 39, 0.86) |
Card/panel backgrounds with transparency |
--surface-strong |
rgba(17, 24, 46, 0.96) |
High-opacity surfaces (modals, overlays) |
--surface-soft |
rgba(255, 255, 255, 0.04) |
Subtle backgrounds (hover states, inputs) |
--line |
rgba(169, 184, 255, 0.16) |
Standard borders and dividers |
--line-strong |
rgba(169, 184, 255, 0.28) |
Emphasized borders |
--text |
#f3f5fb |
Primary text color |
--muted |
#93a0bf |
Secondary text, labels, metadata |
--accent |
#4e85ff |
Primary brand color, links, active states |
--accent-strong |
#1ed6a4 |
Success states, highlights, CTAs |
--danger |
#ff7d8d |
Error states, destructive actions |
--success |
#73f2bb |
Success messages, confirmations |
--shadow |
0 24px 80px rgba(3, 8, 22, 0.45) |
Card and modal shadows |
--ambient-a |
rgba(78, 133, 255, 0.22) |
Ambient glow effect (blue) |
--ambient-b |
rgba(30, 214, 164, 0.18) |
Ambient glow effect (green) |
--backdrop |
rgba(4, 8, 18, 0.76) |
Modal backdrop overlay |
--modal-bg |
linear-gradient(180deg, rgba(20, 27, 50, 0.98), rgba(12, 18, 34, 0.98)) |
Modal background gradient |
| Token | Value | Usage |
|---|---|---|
--page-bg |
#f5f7fa |
Main page background |
--surface |
#ffffff |
Card/panel backgrounds |
--surface-strong |
rgba(255, 255, 255, 0.92) |
High-opacity surfaces |
--surface-soft |
rgba(0, 0, 0, 0.06) |
Subtle backgrounds |
--line |
rgba(0, 0, 0, 0.8) |
Standard borders and dividers |
--line-strong |
rgba(0, 0, 0, 0.12) |
Emphasized borders |
--text |
#1a2332 |
Primary text color |
--muted |
#64748b |
Secondary text, labels, metadata |
--accent |
#2563eb |
Primary brand color, links, active states |
--accent-strong |
#059669 |
Success states, highlights, CTAs |
--danger |
#dc2626 |
Error states, destructive actions |
--success |
#10b981 |
Success messages, confirmations |
--shadow |
0 12px 40px rgba(78, 133, 255, 0.1) |
Card and modal shadows |
--ambient-a |
rgba(78, 133, 255, 0.08) |
Ambient glow effect (blue) |
--ambient-b |
rgba(30, 214, 164, 0.06) |
Ambient glow effect (green) |
--backdrop |
rgba(255, 255, 255, 0.8) |
Modal backdrop overlay |
--modal-bg |
linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 255, 0.98)) |
Modal background gradient |
- Backgrounds: Use
--page-bgfor main content,--surfacefor cards,--surface-softfor inputs/hover states - Text: Use
--textfor primary content,--mutedfor secondary text and labels - Borders: Use
--linefor standard borders,--line-strongfor emphasized borders - Actions: Use
--accentfor primary actions/links,--accent-strongfor success states,--dangerfor destructive actions - Shadows: Use the predefined
--shadowtoken; don't create custom shadows
All shared components are located in src/components/. Use these components instead of building custom UI elements.
Displays API information in a card format for marketplace listings.
Props:
api: any- API object containing name, description, provider, price, rating, tagsonViewDetails?: (api: any) => void- Callback when "View Details" is clicked
Variants:
ApiCard- Standard card with hover effectsApiCardSkeleton- Loading state with skeleton placeholders
Visual Spec:
- Min-height: 220px
- Padding: 12px
- Border: 1px solid with hover state
- Hover: Lift effect (translateY -4px), enhanced shadow, accent border
- Background: Uses
--surface-softtoken - Tags: Rounded pills (8px radius) with
--mutedtext
States:
- Default: Subtle border, no shadow
- Hover: Accent border (#4666ff), shadow, lift effect
- Focus: Keyboard accessible with tabIndex=0, Enter key triggers onViewDetails
- Loading: Skeleton variant with placeholder elements
Accessibility:
tabIndex={0}for keyboard navigationonKeyDownhandles Enter keyaria-labelon action buttons- Semantic
<article>element
Usage Example:
<ApiCard
api={apiData}
onViewDetails={(api) => navigate(`/api/${api.id}`)}
/>Navigation breadcrumb showing page hierarchy.
Props:
items: Array<{ label: string; href: string; isCurrent?: boolean }>- Array of breadcrumb items
Visual Spec:
- Font size: 0.875rem
- Spacing: 8px gap between items
- Separator: "→" arrow (aria-hidden)
- Current page: Bold,
--textcolor - Links:
--accentcolor, no underline - Padding-left: 32px
States:
- Default: Muted separator, accent links
- Focus: Custom outline (2px solid
--accent) on links - Current: Non-clickable, bold text
Accessibility:
aria-label="breadcrumb"on navaria-current="page"on current item- Keyboard navigation with Enter/Space
- Custom focus management (onFocus/onBlur)
Usage Example:
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Marketplace", href: "/marketplace" },
{ label: "API Details", isCurrent: true }
]}
/>Tabbed code snippet display with copy-to-clipboard functionality.
Props:
snippets: Record<string, string>- Object mapping language names to code stringsdefaultLanguage?: string- Initial active tab (defaults to first key)
Visual Spec:
- Container:
preview-cardclass, bordered - Header: Flex layout, tabs on left, copy button on right
- Tabs: Uppercase, 11px font, 4px padding, rounded (4px)
- Active tab:
--bg-highlightbackground, bordered - Code area: 16px padding, monospace font, 13px size
- Copy button: Ghost button style, 75px min-width
States:
- Default: Shows code for active language
- Tab switch: Instant, no animation
- Copy: Button shows "Copied!" with checkmark for 1.5s
- Focus: Standard focus ring on interactive elements
Accessibility:
role="tablist"on tab containerrole="tab"andaria-selectedon tabsaria-labelon copy button- Keyboard navigation between tabs
Usage Example:
<CodeExample
snippets={{
bash: "curl https://api.callora.com/v1/endpoint",
javascript: "fetch('https://api.callora.com/v1/endpoint')"
}}
defaultLanguage="bash"
/>Displayed when no results are found (e.g., empty search results).
Props:
title?: string- Heading text (default: "No APIs found")message?: string- Subtitle text (default: "Try adjusting your filters")
Visual Spec:
- Layout: Centered, 32px padding
- Icon: 160px width, SVG illustration with low opacity
- Heading:
h3element, no margin - Message:
--mutedcolor, 8px top margin
States:
- Static (no interactive states)
Accessibility:
- Semantic heading structure
- Uses
--mutedtoken for secondary text
Usage Example:
<EmptyState
title="No results found"
message="Try different search terms"
/>Sidebar for filtering marketplace results.
Props:
selectedCategories: Set<string>- Currently selected categoriestoggleCategory: (c: string) => void- Toggle category selectionminPrice: number | null- Minimum price filtermaxPrice: number | null- Maximum price filtersetMinPrice: (v: number | null) => void- Set minimum pricesetMaxPrice: (v: number | null) => void- Set maximum pricepopularity: string- Popularity sort optionsetPopularity: (p: string) => void- Set popularity sortclearFilters: () => void- Reset all filters
Visual Spec:
- Layout: Vertical sections with 12px margin
- Categories: Checkbox list, 8px gap
- Price range: Two number inputs side-by-side
- Popularity: Select dropdown
- Clear button: Ghost button style
States:
- Default: All filters visible
- Checked: Checkbox shows selected state
- Focused: Standard focus ring on inputs
Accessibility:
- Semantic
<aside>element - Labels for all form controls
- Keyboard navigation through checkboxes
Usage Example:
<FiltersSidebar
selectedCategories={selectedCategories}
toggleCategory={toggleCategory}
minPrice={minPrice}
maxPrice={maxPrice}
setMinPrice={setMinPrice}
setMaxPrice={setMaxPrice}
popularity={popularity}
setPopularity={setPopularity}
clearFilters={clearFilters}
/>404 error page with search functionality.
Props:
onGoHome: () => void- Callback to navigate to home
Visual Spec:
- Layout: Centered,
surfaceclass,placeholder-cardclass - Error code: Large "404" text (clamp 4rem to 8rem)
- Heading: "Page Not Found"
- Actions: Primary and secondary buttons
- Search: Input with search button
- Links: Navigation links to main sections
States:
- Default: Shows error message and navigation options
- Search: Shows helper message if no match found
- Focus: Standard focus ring on all interactive elements
Accessibility:
aria-labelledby="not-found-title"on sectionrole="search"on search formrole="status"andaria-live="polite"on search feedback- Semantic heading structure
Usage Example:
<NotFound onGoHome={() => navigate("/")} />Search input with clear button and keyboard shortcuts.
Props:
value: string- Current search valueonChange: (v: string) => void- Update search valueplaceholder?: string- Input placeholder (default: "Search APIs, providers, tags...")onSearch?: () => void- Callback on Enter key
Visual Spec:
- Layout: Flex container, 8px gap
- Input wrapper: 8px padding, 8px border-radius,
--surface-softbackground - Icon: 18x18px search icon
- Input: Transparent background, no border
- Clear button: 16x16px X icon, appears when value exists
States:
- Default: Transparent border
- Focused: 2px solid
--primaryborder and outline - Has value: Clear button visible
- Hover: Clear button color changes to
--text
Accessibility:
aria-label="Search APIs"on inputaria-label="Clear search"on clear button- Keyboard shortcuts: Escape clears, Enter searches
role="search"on container
Usage Example:
<SearchBar
value={searchQuery}
onChange={setSearchQuery}
onSearch={handleSearch}
/>Server error display with retry functionality.
Props:
onRetry?: () => void | Promise<void>- Retry callback (optional)requestId?: string- Request ID for support (displayed masked)title?: string- Error heading (default: "Something went wrong on our end")description?: string- Error message (default: standard copy)
Visual Spec:
- Layout: Centered, max-width 400px, 48px padding
- Icon: 80x80px circle with warning icon
- Heading: clamp 1.5rem to 1.8rem, 600 weight
- Retry button: Primary button, 140px min-width, 48px min-height
- Request ID: Monospace font, copy button, separator line
States:
- Default: Shows error message
- Retrying: Button shows "Retrying…", disabled,
aria-busy - Copied: Request ID button shows "Copied!" for 2s
- Focus: Retry button auto-focused on mount if onRetry provided
Accessibility:
role="alert"on sectionaria-busyon retry button during retryaria-live="polite"andaria-atomicfor copy feedback (screen reader only)aria-labelon copy button- Focus management on mount
Usage Example:
<ServerError
onRetry={fetchData}
requestId="req_abc123"
title="Connection failed"
description="Unable to reach the server. Please check your connection."
/>Loading placeholder for content.
Props:
width?: string | number- Skeleton widthheight?: string | number- Skeleton heightborderRadius?: string | number- Border radiusstyle?: CSSProperties- Additional inline stylesclassName?: string- Additional CSS classes
Visual Spec:
- Class:
skeleton(defined in CSS) - Animation: Shimmer effect (defined in CSS)
- Background: Animated gradient
States:
- Static (no interactive states)
Accessibility:
aria-hiddenshould be set by parent if used as loading indicator
Usage Example:
<Skeleton width={200} height={20} borderRadius={4} />The following utility classes are defined in src/index.css and should be used instead of custom styles:
.primary-button- Primary action button with gradient background.secondary-button- Secondary action button with border.ghost-button- Minimal button with hover effect.close-button- Dismiss/close action button.danger-button- Destructive action button
.surface- Card/panel with border, radius, shadow, backdrop blur.app-shell- Main app container with padding.hero-grid- Two-column hero layout.modal-grid- Two-column modal layout
.brand- Large brand heading.eyebrow- Small uppercase label.helper-text- Secondary/muted text
.not-found- 404 page container.server-error- Error page container.placeholder-card- Generic placeholder container
- All interactive elements must have visible focus states
- Use the predefined
--focus-ringtoken for focus indicators - The global CSS suppresses default outlines and restores them via
:focus-visible - Never remove focus styles entirely
- All buttons and links must be keyboard accessible
- Use semantic HTML elements (
<button>,<a>,<input>) - Provide keyboard shortcuts where appropriate (e.g., Escape to close modals)
- Ensure tab order follows logical reading order
- Use
aria-labelfor icon-only buttons - Use
aria-current="page"for current navigation items - Use
aria-live="polite"for dynamic content updates - Use
roleattributes when semantic HTML isn't sufficient - Use
aria-busyfor loading states
- All text must meet WCAG AA contrast ratios (4.5:1 for normal text, 3:1 for large text)
- The design tokens are pre-configured to meet these standards
- Never override token colors with custom values that may violate contrast requirements
- Theme transitions are wrapped in
@media (prefers-reduced-motion: no-preference) - Respect user's motion preferences
- Avoid unnecessary animations
- Check existing components first - Reuse before creating new
- Use design tokens - Never hardcode colors, spacing, or shadows
- Follow the component patterns - Match existing prop interfaces and styling
- Test in both themes - Verify appearance in light and dark modes
- Test accessibility - Verify keyboard navigation and screen reader compatibility
- Document your component - Add this file with props, states, and accessibility notes
- Preserve token usage - Don't replace tokens with inline values
- Maintain accessibility - Keep ARIA attributes and keyboard support
- Update documentation - Keep this file in sync with your changes
- Test regressions - Verify existing functionality still works
- No hardcoded hex colors or inline styles
- All colors use CSS custom properties (var(--token-name))
- Component uses existing CSS classes where applicable
- Keyboard navigation works (Tab, Enter, Escape)
- Focus states are visible
- ARIA attributes are present where needed
- Works in both light and dark themes
- Respects reduced motion preference
- Component is documented in this guide
When you modify design tokens or components:
- Update the relevant section in this document
- Cross-check documented values against
src/index.css - Verify component props match the implementation
- Test the documented examples
This document should be treated as part of the codebase - keep it accurate and up-to-date.