Files
agents/plugins/ui-design/skills/visual-design-foundations/references/spacing-iconography.md
Seth Hobson 1e54d186fe feat(ui-design): add comprehensive UI/UX design plugin v1.0.0
New plugin covering mobile (iOS, Android, React Native) and web
applications with modern design patterns, accessibility, and design systems.

Components:
- 9 skills: design-system-patterns, accessibility-compliance, responsive-design,
  mobile-ios-design, mobile-android-design, react-native-design,
  web-component-design, interaction-design, visual-design-foundations
- 4 commands: design-review, create-component, accessibility-audit, design-system-setup
- 3 agents: ui-designer, accessibility-expert, design-system-architect

Marketplace updated:
- Version bumped to 1.3.4
- 102 agents (+3), 116 skills (+9)
2026-01-19 16:22:13 -05:00

11 KiB

Spacing and Iconography Reference

Spacing Systems

8-Point Grid System

The 8-point grid is the industry standard for consistent spacing.

:root {
  /* Base spacing unit */
  --space-unit: 0.25rem; /* 4px */

  /* Spacing scale */
  --space-0: 0;
  --space-px: 1px;
  --space-0-5: calc(var(--space-unit) * 0.5);  /* 2px */
  --space-1: var(--space-unit);                 /* 4px */
  --space-1-5: calc(var(--space-unit) * 1.5);  /* 6px */
  --space-2: calc(var(--space-unit) * 2);      /* 8px */
  --space-2-5: calc(var(--space-unit) * 2.5);  /* 10px */
  --space-3: calc(var(--space-unit) * 3);      /* 12px */
  --space-3-5: calc(var(--space-unit) * 3.5);  /* 14px */
  --space-4: calc(var(--space-unit) * 4);      /* 16px */
  --space-5: calc(var(--space-unit) * 5);      /* 20px */
  --space-6: calc(var(--space-unit) * 6);      /* 24px */
  --space-7: calc(var(--space-unit) * 7);      /* 28px */
  --space-8: calc(var(--space-unit) * 8);      /* 32px */
  --space-9: calc(var(--space-unit) * 9);      /* 36px */
  --space-10: calc(var(--space-unit) * 10);    /* 40px */
  --space-11: calc(var(--space-unit) * 11);    /* 44px */
  --space-12: calc(var(--space-unit) * 12);    /* 48px */
  --space-14: calc(var(--space-unit) * 14);    /* 56px */
  --space-16: calc(var(--space-unit) * 16);    /* 64px */
  --space-20: calc(var(--space-unit) * 20);    /* 80px */
  --space-24: calc(var(--space-unit) * 24);    /* 96px */
  --space-28: calc(var(--space-unit) * 28);    /* 112px */
  --space-32: calc(var(--space-unit) * 32);    /* 128px */
}

Semantic Spacing Tokens

:root {
  /* Component-level spacing */
  --spacing-xs: var(--space-1);    /* 4px - tight spacing */
  --spacing-sm: var(--space-2);    /* 8px - compact spacing */
  --spacing-md: var(--space-4);    /* 16px - default spacing */
  --spacing-lg: var(--space-6);    /* 24px - comfortable spacing */
  --spacing-xl: var(--space-8);    /* 32px - loose spacing */
  --spacing-2xl: var(--space-12);  /* 48px - generous spacing */
  --spacing-3xl: var(--space-16);  /* 64px - section spacing */

  /* Specific use cases */
  --spacing-inline: var(--space-2);      /* Between inline elements */
  --spacing-stack: var(--space-4);       /* Between stacked elements */
  --spacing-inset: var(--space-4);       /* Padding inside containers */
  --spacing-section: var(--space-16);    /* Between major sections */
  --spacing-page: var(--space-24);       /* Page margins */
}

Spacing Utility Functions

// Tailwind-like spacing scale generator
function createSpacingScale(baseUnit: number = 4): Record<string, string> {
  const scale: Record<string, string> = {
    '0': '0',
    'px': '1px',
  };

  const multipliers = [
    0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10,
    11, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48,
    52, 56, 60, 64, 72, 80, 96,
  ];

  for (const m of multipliers) {
    const key = m % 1 === 0 ? String(m) : String(m).replace('.', '-');
    scale[key] = `${baseUnit * m}px`;
  }

  return scale;
}

Layout Spacing Patterns

Container Queries for Spacing

/* Responsive spacing based on container size */
.card {
  container-type: inline-size;
  padding: var(--space-4);
}

@container (min-width: 400px) {
  .card {
    padding: var(--space-6);
  }
}

@container (min-width: 600px) {
  .card {
    padding: var(--space-8);
  }
}

Negative Space Patterns

/* Asymmetric spacing for visual hierarchy */
.hero-section {
  padding-top: var(--space-24);
  padding-bottom: var(--space-16);
}

/* Content breathing room */
.prose > * + * {
  margin-top: var(--space-4);
}

.prose > h2 + * {
  margin-top: var(--space-2);
}

.prose > * + h2 {
  margin-top: var(--space-8);
}

Icon Systems

Icon Size Scale

:root {
  /* Icon sizes aligned to spacing grid */
  --icon-xs: 12px;   /* Inline decorators */
  --icon-sm: 16px;   /* Small UI elements */
  --icon-md: 20px;   /* Default size */
  --icon-lg: 24px;   /* Emphasis */
  --icon-xl: 32px;   /* Large displays */
  --icon-2xl: 48px;  /* Hero icons */

  /* Touch target sizes */
  --touch-target-min: 44px;  /* WCAG minimum */
  --touch-target-comfortable: 48px;
}

SVG Icon Component

import { forwardRef, type SVGProps } from 'react';

interface IconProps extends SVGProps<SVGSVGElement> {
  name: string;
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
  label?: string;
}

const sizeMap = {
  xs: 12,
  sm: 16,
  md: 20,
  lg: 24,
  xl: 32,
  '2xl': 48,
};

export const Icon = forwardRef<SVGSVGElement, IconProps>(
  ({ name, size = 'md', label, className, ...props }, ref) => {
    const pixelSize = sizeMap[size];

    return (
      <svg
        ref={ref}
        width={pixelSize}
        height={pixelSize}
        className={`inline-block flex-shrink-0 ${className}`}
        aria-hidden={!label}
        aria-label={label}
        role={label ? 'img' : undefined}
        {...props}
      >
        <use href={`/icons.svg#${name}`} />
      </svg>
    );
  }
);

Icon.displayName = 'Icon';

Icon Button Patterns

interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  icon: string;
  label: string;
  size?: 'sm' | 'md' | 'lg';
  variant?: 'solid' | 'ghost' | 'outline';
}

const sizeClasses = {
  sm: 'p-1.5',        /* 32px total with 16px icon */
  md: 'p-2',          /* 40px total with 20px icon */
  lg: 'p-2.5',        /* 48px total with 24px icon */
};

const iconSizes = {
  sm: 'sm' as const,
  md: 'md' as const,
  lg: 'lg' as const,
};

export function IconButton({
  icon,
  label,
  size = 'md',
  variant = 'ghost',
  className,
  ...props
}: IconButtonProps) {
  return (
    <button
      className={`
        inline-flex items-center justify-center rounded-lg
        transition-colors focus-visible:outline-none focus-visible:ring-2
        ${sizeClasses[size]}
        ${variant === 'solid' && 'bg-blue-600 text-white hover:bg-blue-700'}
        ${variant === 'ghost' && 'hover:bg-gray-100'}
        ${variant === 'outline' && 'border border-gray-300 hover:bg-gray-50'}
        ${className}
      `}
      aria-label={label}
      {...props}
    >
      <Icon name={icon} size={iconSizes[size]} />
    </button>
  );
}

Icon Sprite Generation

// Build script for SVG sprite
import { readdir, readFile, writeFile } from 'fs/promises';
import { optimize } from 'svgo';

async function buildIconSprite(iconDir: string, outputPath: string) {
  const files = await readdir(iconDir);
  const svgFiles = files.filter((f) => f.endsWith('.svg'));

  const symbols = await Promise.all(
    svgFiles.map(async (file) => {
      const content = await readFile(`${iconDir}/${file}`, 'utf-8');
      const name = file.replace('.svg', '');

      // Optimize SVG
      const result = optimize(content, {
        plugins: [
          'removeDoctype',
          'removeXMLProcInst',
          'removeComments',
          'removeMetadata',
          'removeTitle',
          'removeDesc',
          'removeUselessDefs',
          'removeEditorsNSData',
          'removeEmptyAttrs',
          'removeHiddenElems',
          'removeEmptyText',
          'removeEmptyContainers',
          'convertStyleToAttrs',
          'convertColors',
          'convertPathData',
          'convertTransform',
          'removeUnknownsAndDefaults',
          'removeNonInheritableGroupAttrs',
          'removeUselessStrokeAndFill',
          'removeUnusedNS',
          'cleanupNumericValues',
          'cleanupListOfValues',
          'moveElemsAttrsToGroup',
          'moveGroupAttrsToElems',
          'collapseGroups',
          'mergePaths',
        ],
      });

      // Extract viewBox and content
      const viewBoxMatch = result.data.match(/viewBox="([^"]+)"/);
      const viewBox = viewBoxMatch ? viewBoxMatch[1] : '0 0 24 24';
      const innerContent = result.data
        .replace(/<svg[^>]*>/, '')
        .replace(/<\/svg>/, '');

      return `<symbol id="${name}" viewBox="${viewBox}">${innerContent}</symbol>`;
    })
  );

  const sprite = `<svg xmlns="http://www.w3.org/2000/svg" style="display:none">${symbols.join('')}</svg>`;

  await writeFile(outputPath, sprite);
  console.log(`Generated sprite with ${symbols.length} icons`);
}

Icon Libraries Integration

// Lucide React
import { Home, Settings, User, Search } from 'lucide-react';

function Navigation() {
  return (
    <nav className="flex gap-4">
      <NavItem icon={Home} label="Home" />
      <NavItem icon={Search} label="Search" />
      <NavItem icon={Settings} label="Settings" />
      <NavItem icon={User} label="Profile" />
    </nav>
  );
}

// Heroicons
import { HomeIcon, Cog6ToothIcon } from '@heroicons/react/24/outline';
import { HomeIcon as HomeIconSolid } from '@heroicons/react/24/solid';

function ToggleIcon({ active }: { active: boolean }) {
  const Icon = active ? HomeIconSolid : HomeIcon;
  return <Icon className="w-6 h-6" />;
}

// Radix Icons
import { HomeIcon, GearIcon } from '@radix-ui/react-icons';

Sizing Systems

Element Sizing Scale

:root {
  /* Fixed sizes */
  --size-4: 1rem;      /* 16px */
  --size-5: 1.25rem;   /* 20px */
  --size-6: 1.5rem;    /* 24px */
  --size-8: 2rem;      /* 32px */
  --size-10: 2.5rem;   /* 40px */
  --size-12: 3rem;     /* 48px */
  --size-14: 3.5rem;   /* 56px */
  --size-16: 4rem;     /* 64px */
  --size-20: 5rem;     /* 80px */
  --size-24: 6rem;     /* 96px */
  --size-32: 8rem;     /* 128px */

  /* Component heights */
  --height-input-sm: var(--size-8);   /* 32px */
  --height-input-md: var(--size-10);  /* 40px */
  --height-input-lg: var(--size-12);  /* 48px */

  /* Avatar sizes */
  --avatar-xs: var(--size-6);   /* 24px */
  --avatar-sm: var(--size-8);   /* 32px */
  --avatar-md: var(--size-10);  /* 40px */
  --avatar-lg: var(--size-12);  /* 48px */
  --avatar-xl: var(--size-16);  /* 64px */
  --avatar-2xl: var(--size-24); /* 96px */
}

Aspect Ratios

.aspect-ratios {
  /* Standard ratios */
  --aspect-square: 1 / 1;
  --aspect-video: 16 / 9;
  --aspect-photo: 4 / 3;
  --aspect-portrait: 3 / 4;
  --aspect-cinema: 21 / 9;
  --aspect-golden: 1.618 / 1;
}

/* Usage */
.thumbnail {
  aspect-ratio: var(--aspect-video);
  object-fit: cover;
}

.avatar {
  aspect-ratio: var(--aspect-square);
  border-radius: 50%;
}

Border Radius Scale

:root {
  --radius-none: 0;
  --radius-sm: 0.125rem;   /* 2px */
  --radius-default: 0.25rem; /* 4px */
  --radius-md: 0.375rem;   /* 6px */
  --radius-lg: 0.5rem;     /* 8px */
  --radius-xl: 0.75rem;    /* 12px */
  --radius-2xl: 1rem;      /* 16px */
  --radius-3xl: 1.5rem;    /* 24px */
  --radius-full: 9999px;

  /* Component-specific */
  --radius-button: var(--radius-md);
  --radius-input: var(--radius-md);
  --radius-card: var(--radius-lg);
  --radius-modal: var(--radius-xl);
  --radius-badge: var(--radius-full);
}