Visual Design Foundations
Seth HobsonCommunityCreated by Seth Hobson
- Skill
- Guidelines
- Design and creative
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
Visual Design Foundations provides a structured approach to creating consistent digital interfaces. It offers technical guidance on implementing design tokens, establishing modular typography scales, applying 8-point grid spacing systems, and defining semantic color palettes. The skill includes practical implementation patterns for CSS variables, Tailwind configuration, and accessible component design to ensure visual hierarchy and readability.
What it does
- Defines modular typography scales and line-height guidelines.
- Provides an 8-point grid system for consistent layout spacing.
- Outlines semantic color token strategies for light and dark modes.
- Offers patterns for responsive typography using clamp functions.
- Includes technical guidance for icon sizing and component integration.
- Provides methods for programmatic contrast ratio verification.
When to use it
- When establishing design tokens for a new project.
- When building or refining a design system or style guide.
- When improving visual hierarchy and readability in an interface.
- When implementing dark mode or theme support.
- When auditing existing designs for visual consistency.
Advantages
- Promotes visual harmony through standardized spacing and sizing.
- Supports accessibility through clear contrast and typography guidelines.
- Facilitates maintainability by using semantic tokens.
- Provides ready-to-use code patterns for CSS and Tailwind.
- Reduces design debt by eliminating arbitrary magic numbers.
Usage examples
- “How do I set up a modular typography scale in CSS?”
- The agent provides a CSS variable block defining a scale from xs to 5xl with corresponding line-height recommendations.
- “What is the recommended spacing for a card component?”
- The agent suggests using the 8-point grid system, specifically recommending 16-24px padding for cards.
- “How can I implement semantic color tokens for a button?”
- The agent provides a CSS example mapping button background and hover states to semantic color tokens.
- “How do I ensure my text meets accessibility standards?”
- The agent provides a TypeScript function to calculate contrast ratios and lists WCAG minimum ratio requirements.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/wshobson/visual-design-foundations/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip visual-design-foundations.zip -d .agents/skills
Documentation by Seth Hobson
Visual Design Foundations
Build cohesive, accessible visual systems using typography, color, spacing, and iconography fundamentals.
When to Use This Skill
Establishing design tokens for a new project
Creating or refining a spacing and sizing system
Selecting and pairing typefaces
Building accessible color palettes
Designing icon systems and visual assets
Improving visual hierarchy and readability
Auditing designs for visual consistency
Implementing dark mode or theming
Core Systems
1. Typography Scale
Modular Scale (ratio-based sizing):
:root {
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-lg: 1.125rem; /* 18px */
--font-size-xl: 1.25rem; /* 20px */
--font-size-2xl: 1.5rem; /* 24px */
--font-size-3xl: 1.875rem; /* 30px */
--font-size-4xl: 2.25rem; /* 36px */
--font-size-5xl: 3rem; /* 48px */
}Line Height Guidelines:
| Text Type | Line Height |
|---|---|
| Headings | 1.1 - 1.3 |
| Body text | 1.5 - 1.7 |
| UI labels | 1.2 - 1.4 |
2. Spacing System
8-point grid (industry standard):
:root {
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-5: 1.25rem; /* 20px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-10: 2.5rem; /* 40px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
}3. Color System
Semantic color tokens:
:root {
/* Brand */
--color-primary: #2563eb;
--color-primary-hover: #1d4ed8;
--color-primary-active: #1e40af;
/* Semantic */
--color-success: #16a34a;
--color-warning: #ca8a04;
--color-error: #dc2626;
--color-info: #0891b2;
/* Neutral */
--color-gray-50: #f9fafb;
--color-gray-100: #f3f4f6;
--color-gray-200: #e5e7eb;
--color-gray-300: #d1d5db;
--color-gray-400: #9ca3af;
--color-gray-500: #6b7280;
--color-gray-600: #4b5563;
--color-gray-700: #374151;
--color-gray-800: #1f2937;
--color-gray-900: #111827;
}Quick Start: Design Tokens in Tailwind
// tailwind.config.js
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ["Inter", "system-ui", "sans-serif"],
mono: ["JetBrains Mono", "monospace"],
},
fontSize: {
xs: ["0.75rem", { lineHeight: "1rem" }],
sm: ["0.875rem", { lineHeight: "1.25rem" }],
base: ["1rem", { lineHeight: "1.5rem" }],
lg: ["1.125rem", { lineHeight: "1.75rem" }],
xl: ["1.25rem", { lineHeight: "1.75rem" }],
"2xl": ["1.5rem", { lineHeight: "2rem" }],
},
colors: {
brand: {
50: "#eff6ff",
500: "#3b82f6",
600: "#2563eb",
700: "#1d4ed8",
},
},
spacing: {
// Extends default with custom values
18: "4.5rem",
88: "22rem",
},
},
},
};Typography Best Practices
Font Pairing
Safe combinations:
Heading: Inter / Body: Inter (single family)
Heading: Playfair Display / Body: Source Sans Pro (contrast)
Heading: Space Grotesk / Body: IBM Plex Sans (geometric)
Responsive Typography
/* Fluid typography using clamp() */
h1 {
font-size: clamp(2rem, 5vw + 1rem, 3.5rem);
line-height: 1.1;
}
p {
font-size: clamp(1rem, 2vw + 0.5rem, 1.125rem);
line-height: 1.6;
max-width: 65ch; /* Optimal reading width */
}Font Loading
/* Prevent layout shift */
@font-face {
font-family: "Inter";
src: url("/fonts/Inter.woff2") format("woff2");
font-display: swap;
font-weight: 400 700;
}Color Theory
Contrast Requirements (WCAG)
| Element | Minimum Ratio |
|---|---|
| Body text | 4.5:1 (AA) |
| Large text (18px+) | 3:1 (AA) |
| UI components | 3:1 (AA) |
| Enhanced | 7:1 (AAA) |
Dark Mode Strategy
:root {
--bg-primary: #ffffff;
--bg-secondary: #f9fafb;
--text-primary: #111827;
--text-secondary: #6b7280;
--border: #e5e7eb;
}
[data-theme="dark"] {
--bg-primary: #111827;
--bg-secondary: #1f2937;
--text-primary: #f9fafb;
--text-secondary: #9ca3af;
--border: #374151;
}Color Accessibility
// Check contrast programmatically
function getContrastRatio(foreground: string, background: string): number {
const getLuminance = (hex: string) => {
const rgb = hexToRgb(hex);
const [r, g, b] = rgb.map((c) => {
c = c / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
const l1 = getLuminance(foreground);
const l2 = getLuminance(background);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}Spacing Guidelines
Component Spacing
Card padding: 16-24px (--space-4 to --space-6)
Section gap: 32-64px (--space-8 to --space-16)
Form field gap: 16-24px (--space-4 to --space-6)
Button padding: 8-16px vertical, 16-24px horizontal
Icon-text gap: 8px (--space-2)Visual Rhythm
/* Consistent vertical rhythm */
.prose > * + * {
margin-top: var(--space-4);
}
.prose > h2 + * {
margin-top: var(--space-2);
}
.prose > * + h2 {
margin-top: var(--space-8);
}Iconography
Icon Sizing System
:root {
--icon-xs: 12px;
--icon-sm: 16px;
--icon-md: 20px;
--icon-lg: 24px;
--icon-xl: 32px;
}Icon Component
interface IconProps {
name: string;
size?: "xs" | "sm" | "md" | "lg" | "xl";
className?: string;
}
const sizeMap = {
xs: 12,
sm: 16,
md: 20,
lg: 24,
xl: 32,
};
export function Icon({ name, size = "md", className }: IconProps) {
return (
<svg
width={sizeMap[size]}
height={sizeMap[size]}
className={cn("inline-block flex-shrink-0", className)}
aria-hidden="true"
>
<use href={`/icons.svg#${name}`} />
</svg>
);
}Best Practices
Establish Constraints: Limit choices to maintain consistency
Document Decisions: Create a living style guide
Test Accessibility: Verify contrast, sizing, touch targets
Use Semantic Tokens: Name by purpose, not appearance
Design Mobile-First: Start with constraints, add complexity
Maintain Vertical Rhythm: Consistent spacing creates harmony
Limit Font Weights: 2-3 weights per family is sufficient
Common Issues
Inconsistent Spacing: Not using a defined scale
Poor Contrast: Failing WCAG requirements
Font Overload: Too many families or weights
Magic Numbers: Arbitrary values instead of tokens
Missing States: Forgetting hover, focus, disabled
No Dark Mode Plan: Retrofitting is harder than planning
Files
- ATTRIBUTION.mdAdded by AutoGineer993 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md7.0 KiB
- references/color-systems.md10.5 KiB
- references/spacing-iconography.md10.4 KiB
- references/typography-systems.md8.7 KiB
references/color-systems.md
Color Systems Reference
Color Palette Generation
Perceptually Uniform Scales
Using OKLCH for perceptually uniform color scales:
/* OKLCH: Lightness, Chroma, Hue */
:root {
/* Generate a blue scale with consistent perceived lightness steps */
--blue-50: oklch(97% 0.02 250);
--blue-100: oklch(93% 0.04 250);
--blue-200: oklch(86% 0.08 250);
--blue-300: oklch(75% 0.12 250);
--blue-400: oklch(65% 0.16 250);
--blue-500: oklch(55% 0.2 250); /* Primary */
--blue-600: oklch(48% 0.18 250);
--blue-700: oklch(40% 0.16 250);
--blue-800: oklch(32% 0.12 250);
--blue-900: oklch(25% 0.08 250);
--blue-950: oklch(18% 0.05 250);
}Programmatic Scale Generation
function generateColorScale(
hue: number,
saturation: number = 100,
): Record<string, string> {
const lightnessStops = [
{ name: "50", l: 97 },
{ name: "100", l: 93 },
{ name: "200", l: 85 },
{ name: "300", l: 75 },
{ name: "400", l: 65 },
{ name: "500", l: 55 },
{ name: "600", l: 45 },
{ name: "700", l: 35 },
{ name: "800", l: 25 },
{ name: "900", l: 18 },
{ name: "950", l: 12 },
];
return Object.fromEntries(
lightnessStops.map(({ name, l }) => [
name,
`hsl(${hue}, ${saturation}%, ${l}%)`,
]),
);
}
// Generate semantic colors
const brand = generateColorScale(220); // Blue
const success = generateColorScale(142); // Green
const warning = generateColorScale(38); // Amber
const error = generateColorScale(0); // RedSemantic Color Tokens
Two-Tier Token System
/* Tier 1: Primitive colors (raw values) */
:root {
--primitive-blue-500: #3b82f6;
--primitive-blue-600: #2563eb;
--primitive-green-500: #22c55e;
--primitive-red-500: #ef4444;
--primitive-gray-50: #f9fafb;
--primitive-gray-900: #111827;
}
/* Tier 2: Semantic tokens (purpose-based) */
:root {
/* Background */
--color-bg-primary: var(--primitive-gray-50);
--color-bg-secondary: white;
--color-bg-tertiary: var(--primitive-gray-100);
--color-bg-inverse: var(--primitive-gray-900);
/* Text */
--color-text-primary: var(--primitive-gray-900);
--color-text-secondary: var(--primitive-gray-600);
--color-text-tertiary: var(--primitive-gray-400);
--color-text-inverse: white;
--color-text-link: var(--primitive-blue-600);
/* Border */
--color-border-default: var(--primitive-gray-200);
--color-border-strong: var(--primitive-gray-300);
--color-border-focus: var(--primitive-blue-500);
/* Interactive */
--color-interactive-primary: var(--primitive-blue-600);
--color-interactive-primary-hover: var(--primitive-blue-700);
--color-interactive-primary-active: var(--primitive-blue-800);
/* Status */
--color-status-success: var(--primitive-green-500);
--color-status-warning: var(--primitive-amber-500);
--color-status-error: var(--primitive-red-500);
--color-status-info: var(--primitive-blue-500);
}Component Tokens
/* Tier 3: Component-specific tokens */
:root {
/* Button */
--button-bg: var(--color-interactive-primary);
--button-bg-hover: var(--color-interactive-primary-hover);
--button-text: white;
--button-border-radius: 0.375rem;
/* Input */
--input-bg: var(--color-bg-secondary);
--input-border: var(--color-border-default);
--input-border-focus: var(--color-border-focus);
--input-text: var(--color-text-primary);
--input-placeholder: var(--color-text-tertiary);
/* Card */
--card-bg: var(--color-bg-secondary);
--card-border: var(--color-border-default);
--card-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}Dark Mode Implementation
CSS Custom Properties Approach
/* Light theme (default) */
:root {
--color-bg-primary: #ffffff;
--color-bg-secondary: #f9fafb;
--color-bg-tertiary: #f3f4f6;
--color-text-primary: #111827;
--color-text-secondary: #4b5563;
--color-border-default: #e5e7eb;
}
/* Dark theme */
[data-theme="dark"] {
--color-bg-primary: #111827;
--color-bg-secondary: #1f2937;
--color-bg-tertiary: #374151;
--color-text-primary: #f9fafb;
--color-text-secondary: #9ca3af;
--color-border-default: #374151;
}
/* System preference */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--color-bg-primary: #111827;
/* ... dark theme values */
}
}React Theme Context
import { createContext, useContext, useEffect, useState } from "react";
type Theme = "light" | "dark" | "system";
interface ThemeContextValue {
theme: Theme;
setTheme: (theme: Theme) => void;
resolvedTheme: "light" | "dark";
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>("system");
const [resolvedTheme, setResolvedTheme] = useState<"light" | "dark">("light");
useEffect(() => {
const root = document.documentElement;
if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light";
setResolvedTheme(systemTheme);
root.setAttribute("data-theme", systemTheme);
} else {
setResolvedTheme(theme);
root.setAttribute("data-theme", theme);
}
}, [theme]);
useEffect(() => {
if (theme !== "system") return;
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handler = (e: MediaQueryListEvent) => {
const newTheme = e.matches ? "dark" : "light";
setResolvedTheme(newTheme);
document.documentElement.setAttribute("data-theme", newTheme);
};
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, setTheme, resolvedTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) throw new Error("useTheme must be within ThemeProvider");
return context;
}Contrast and Accessibility
WCAG Contrast Checker
function hexToRgb(hex: string): [number, number, number] {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (!result) throw new Error("Invalid hex color");
return [
parseInt(result[1], 16),
parseInt(result[2], 16),
parseInt(result[3], 16),
];
}
function getLuminance(r: number, g: number, b: number): number {
const [rs, gs, bs] = [r, g, b].map((c) => {
c = c / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}
function getContrastRatio(hex1: string, hex2: string): number {
const [r1, g1, b1] = hexToRgb(hex1);
const [r2, g2, b2] = hexToRgb(hex2);
const l1 = getLuminance(r1, g1, b1);
const l2 = getLuminance(r2, g2, b2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
function meetsWCAG(
foreground: string,
background: string,
size: "normal" | "large" = "normal",
level: "AA" | "AAA" = "AA",
): boolean {
const ratio = getContrastRatio(foreground, background);
const requirements = {
normal: { AA: 4.5, AAA: 7 },
large: { AA: 3, AAA: 4.5 },
};
return ratio >= requirements[size][level];
}
// Usage
meetsWCAG("#ffffff", "#3b82f6"); // true (4.5:1 for AA normal)
meetsWCAG("#ffffff", "#60a5fa"); // false (below 4.5:1)Accessible Color Pairs
// Generate accessible text color for any background
function getAccessibleTextColor(backgroundColor: string): string {
const [r, g, b] = hexToRgb(backgroundColor);
const luminance = getLuminance(r, g, b);
// Use white text on dark backgrounds, black on light
return luminance > 0.179 ? "#111827" : "#ffffff";
}
// Find the nearest accessible shade
function findAccessibleShade(
textColor: string,
backgroundScale: string[],
minContrast: number = 4.5,
): string | null {
for (const shade of backgroundScale) {
if (getContrastRatio(textColor, shade) >= minContrast) {
return shade;
}
}
return null;
}Color Harmony
Harmony Functions
type HarmonyType =
| "complementary"
| "triadic"
| "analogous"
| "split-complementary";
function generateHarmony(baseHue: number, type: HarmonyType): number[] {
switch (type) {
case "complementary":
return [baseHue, (baseHue + 180) % 360];
case "triadic":
return [baseHue, (baseHue + 120) % 360, (baseHue + 240) % 360];
case "analogous":
return [(baseHue - 30 + 360) % 360, baseHue, (baseHue + 30) % 360];
case "split-complementary":
return [baseHue, (baseHue + 150) % 360, (baseHue + 210) % 360];
default:
return [baseHue];
}
}
// Generate palette from harmony
function generateHarmoniousPalette(
baseHue: number,
type: HarmonyType,
): Record<string, string> {
const hues = generateHarmony(baseHue, type);
const names = ["primary", "secondary", "tertiary"];
return Object.fromEntries(
hues.map((hue, i) => [names[i] || `color-${i}`, `hsl(${hue}, 70%, 50%)`]),
);
}Color Blindness Considerations
// Simulate color blindness
type ColorBlindnessType = "protanopia" | "deuteranopia" | "tritanopia";
// Matrix transforms for common types
const colorBlindnessMatrices: Record<ColorBlindnessType, number[][]> = {
protanopia: [
[0.567, 0.433, 0],
[0.558, 0.442, 0],
[0, 0.242, 0.758],
],
deuteranopia: [
[0.625, 0.375, 0],
[0.7, 0.3, 0],
[0, 0.3, 0.7],
],
tritanopia: [
[0.95, 0.05, 0],
[0, 0.433, 0.567],
[0, 0.475, 0.525],
],
};
// Best practices for color-blind accessibility:
// 1. Do not rely solely on color to convey information
// 2. Use patterns or icons alongside color
// 3. Ensure sufficient contrast between colors
// 4. Test with color blindness simulators
// 5. Use blue-orange instead of red-green for contrastCSS Color Functions
/* Modern CSS color functions */
.modern-colors {
/* Relative color syntax */
--lighter: hsl(from var(--base-color) h s calc(l + 20%));
--darker: hsl(from var(--base-color) h s calc(l - 20%));
/* Color mixing */
--mixed: color-mix(in srgb, var(--color-1), var(--color-2) 30%);
/* Transparency */
--semi-transparent: rgb(from var(--base-color) r g b / 50%);
/* OKLCH for perceptual uniformity */
--vibrant-blue: oklch(60% 0.2 250);
}
/* Alpha variations */
.alpha-scale {
--color-10: rgb(59 130 246 / 0.1);
--color-20: rgb(59 130 246 / 0.2);
--color-30: rgb(59 130 246 / 0.3);
--color-40: rgb(59 130 246 / 0.4);
--color-50: rgb(59 130 246 / 0.5);
}Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Seth Hobson
- Publisher
- Seth Hobson (Community)
- Source
- wshobson/agents · plugins/ui-design/skills/visual-design-foundations · commit 4bde316
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Seth Hobson.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download visual-design-foundations.zip