Files
simplenote-web/ui/STYLES.md
Hiro 08a88b5e06 feat(ui): add UI/UX design specification for SimpleNote Web
- SPEC.md: Complete UI/UX specification with design principles,
  layout architecture, interaction patterns, data flow, and
  accessibility requirements
- VIEWS.md: Text-based wireframes for Dashboard, Document Viewer,
  Document Editor, and Library Browser views (desktop and mobile)
- COMPONENTS.md: Reusable component library with buttons, inputs,
  cards, tags, navigation, modals, and feedback components
- STYLES.md: Style guide with design tokens (colors, typography,
  spacing), global styles, animations, responsive breakpoints,
  and dark mode implementation

Design follows dark mode first approach inspired by Mission
Control dashboard, with full mobile responsiveness.
2026-03-28 03:36:23 +00:00

20 KiB

SimpleNote Web - Style Guide

1. Design Tokens

Design tokens are CSS custom properties that define the visual language. They enable theming (dark/light modes) and ensure consistency.

1.1 Color Palette

:root {
  /* === Dark Theme (Default) === */
  /* Backgrounds */
  --color-bg: #0f1117;
  --color-surface: #1a1d26;
  --color-surface-raised: #22262f;
  --color-hover: rgba(255, 255, 255, 0.05);
  --color-active: rgba(255, 255, 255, 0.08);

  /* Text */
  --color-text: #e4e6eb;
  --color-text-secondary: #b0b3b8;
  --color-text-muted: #65676b;

  /* Borders */
  --color-border: #303338;
  --color-border-light: #404249;

  /* Accent (Primary brand color - cyan/teal) */
  --color-accent: #00d4aa;
  --color-accent-hover: #00e8bb;
  --color-accent-alpha: rgba(0, 212, 170, 0.15);

  /* Semantic Colors */
  --color-success: #31a065;
  --color-success-bg: rgba(49, 160, 101, 0.15);
  --color-warning: #cf9d2c;
  --color-warning-bg: rgba(207, 157, 44, 0.15);
  --color-danger: #cf4a4a;
  --color-danger-hover: #e05555;
  --color-info: #4a90cf;
  --color-info-bg: rgba(74, 144, 207, 0.15);

  /* Type-specific colors */
  --color-requirement: #00d4aa;  /* Matches accent */
  --color-note: #4a90cf;
  --color-spec: #9b59b6;
  --color-general: #65676b;

  /* Priority */
  --color-priority-high: #cf4a4a;
  --color-priority-medium: #cf9d2c;
  --color-priority-low: #31a065;

  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.25);
  --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.3);
  --shadow-xl: 0 20px 60px rgba(0, 0, 0, 0.4);

  /* Focus ring */
  --focus-ring: 0 0 0 3px rgba(0, 212, 170, 0.4);
}

/* === Light Theme === */
[data-theme="light"] {
  --color-bg: #f5f6f8;
  --color-surface: #ffffff;
  --color-surface-raised: #f0f1f3;
  --color-hover: rgba(0, 0, 0, 0.04);
  --color-active: rgba(0, 0, 0, 0.06);

  --color-text: #1a1d26;
  --color-text-secondary: #4a4f5c;
  --color-text-muted: #8b919d;

  --color-border: #e1e3e8;
  --color-border-light: #ebedf2;

  --color-accent: #00a884;
  --color-accent-hover: #00c49a;
  --color-accent-alpha: rgba(0, 168, 132, 0.12);

  --color-success: #22863a;
  --color-success-bg: rgba(34, 134, 58, 0.1);
  --color-warning: #b08800;
  --color-warning-bg: rgba(176, 136, 0, 0.1);
  --color-danger: #cb2431;
  --color-danger-hover: #d93542;
  --color-info: #0366d6;
  --color-info-bg: rgba(3, 102, 214, 0.1);

  --color-requirement: #00a884;
  --color-note: #0366d6;
  --color-spec: #6f42c1;
  --color-general: #6a737d;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
  --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.1);
  --shadow-xl: 0 20px 60px rgba(0, 0, 0, 0.15);

  --focus-ring: 0 0 0 3px rgba(0, 168, 132, 0.35);
}

1.2 Typography

:root {
  /* Font Families */
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-mono: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Consolas', monospace;

  /* Font Sizes (1.25 scale) */
  --text-xs: 0.64rem;    /* 10.24px */
  --text-sm: 0.8rem;     /* 12.8px */
  --text-base: 1rem;     /* 16px */
  --text-lg: 1.25rem;    /* 20px */
  --text-xl: 1.563rem;   /* 25px */
  --text-2xl: 1.953rem;  /* 31.25px */
  --text-3xl: 2.441rem;  /* 39px */

  /* Line Heights */
  --leading-tight: 1.25;
  --leading-normal: 1.5;
  --leading-relaxed: 1.75;

  /* Font Weights */
  --font-normal: 400;
  --font-medium: 500;
  --font-semibold: 600;
  --font-bold: 700;

  /* Letter Spacing */
  --tracking-tight: -0.025em;
  --tracking-normal: 0;
  --tracking-wide: 0.025em;
  --tracking-wider: 0.05em;
}

/* Google Fonts Import */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');

Typography Scale

Element Size Weight Line Height
H1 2rem (32px) 700 1.2
H2 1.5rem (24px) 600 1.25
H3 1.25rem (20px) 600 1.3
H4 1rem (16px) 600 1.4
Body 1rem (16px) 400 1.5
Small 0.875rem (14px) 400 1.5
Caption 0.75rem (12px) 400 1.4
Code 0.9375rem (15px) 400 1.6

1.3 Spacing

:root {
  /* Spacing scale (4px base) */
  --space-0: 0;
  --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 */
  --space-20: 5rem;     /* 80px */

  /* Border Radius */
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;
  --radius-xl: 12px;
  --radius-2xl: 16px;
  --radius-full: 9999px;

  /* Transitions */
  --transition-fast: 0.1s ease;
  --transition-normal: 0.15s ease;
  --transition-slow: 0.25s ease;
  --transition-spring: 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

1.4 Z-Index Scale

:root {
  --z-base: 0;
  --z-dropdown: 100;
  --z-sticky: 200;
  --z-modal-backdrop: 500;
  --z-modal: 600;
  --z-toast: 700;
  --z-tooltip: 800;
  --z-max: 9999;
}

2. Global Styles

2.1 CSS Reset

*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--color-text);
  background: var(--color-bg);
  min-height: 100vh;
}

img, svg {
  display: block;
  max-width: 100%;
}

button {
  font: inherit;
  cursor: pointer;
}

input, textarea, select {
  font: inherit;
}

a {
  color: var(--color-accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

ul, ol {
  list-style: none;
}

/* Scrollbar styling */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

::-webkit-scrollbar-track {
  background: var(--color-surface);
}

::-webkit-scrollbar-thumb {
  background: var(--color-border);
  border-radius: var(--radius-full);
}

::-webkit-scrollbar-thumb:hover {
  background: var(--color-text-muted);
}

/* Selection */
::selection {
  background: var(--color-accent-alpha);
  color: var(--color-text);
}

2.2 Focus Styles

:focus {
  outline: none;
}

:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}

2.3 Smooth Scrolling

html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

3. Typography Components

3.1 Headings

h1, .h1 {
  font-size: var(--text-2xl);
  font-weight: var(--font-bold);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--color-text);
}

h2, .h2 {
  font-size: var(--text-xl);
  font-weight: var(--font-semibold);
  line-height: 1.25;
  letter-spacing: var(--tracking-tight);
}

h3, .h3 {
  font-size: var(--text-lg);
  font-weight: var(--font-semibold);
  line-height: 1.3;
}

h4, .h4 {
  font-size: var(--text-base);
  font-weight: var(--font-semibold);
  line-height: 1.4;
}

3.2 Body Text

p {
  margin-bottom: var(--space-4);
}

p:last-child {
  margin-bottom: 0;
}

.text-sm {
  font-size: var(--text-sm);
}

.text-xs {
  font-size: var(--text-xs);
}

.text-muted {
  color: var(--color-text-muted);
}

.text-secondary {
  color: var(--color-text-secondary);
}

.font-mono {
  font-family: var(--font-mono);
}

3.3 Prose (Markdown Content)

.prose {
  max-width: 70ch;
  color: var(--color-text);
}

.prose h1 { /* ... */ }
.prose h2 { /* ... */ }
.prose h3 { /* ... */ }
.prose h4 { /* ... */ }

.prose p {
  margin-bottom: 1em;
  line-height: 1.7;
}

.prose ul,
.prose ol {
  margin-bottom: 1em;
  padding-left: 1.5em;
}

.prose ul { list-style-type: disc; }
.prose ol { list-style-type: decimal; }

.prose li {
  margin-bottom: 0.25em;
  line-height: 1.6;
}

.prose code {
  font-family: var(--font-mono);
  font-size: 0.875em;
  background: var(--color-surface);
  padding: 0.125em 0.375em;
  border-radius: var(--radius-sm);
  color: var(--color-accent);
}

.prose pre {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 1rem;
  overflow-x: auto;
  margin-bottom: 1em;
}

.prose pre code {
  background: transparent;
  padding: 0;
  color: var(--color-text);
  font-size: 0.875rem;
  line-height: 1.6;
}

.prose blockquote {
  border-left: 4px solid var(--color-accent);
  padding-left: 1rem;
  margin: 1em 0;
  color: var(--color-text-secondary);
  font-style: italic;
}

.prose table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 1em;
}

.prose th,
.prose td {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-border);
  text-align: left;
}

.prose th {
  background: var(--color-surface);
  font-weight: var(--font-semibold);
}

.prose tr:hover td {
  background: var(--color-hover);
}

/* Task list checkboxes */
.prose input[type="checkbox"] {
  margin-right: 0.5em;
  accent-color: var(--color-accent);
}

.prose a {
  color: var(--color-accent);
  text-decoration: underline;
}

.prose img {
  max-width: 100%;
  border-radius: var(--radius-lg);
  margin: 1em 0;
}

.prose hr {
  border: none;
  border-top: 1px solid var(--color-border);
  margin: 2em 0;
}

4. Animation Guidelines

4.1 Motion Principles

  1. Purposeful: Animation should communicate state changes, not decorate
  2. Quick: Most transitions should be 150-250ms
  3. Responsive: Motion should feel immediate to user input
  4. Respectful: Don't animate if user prefers reduced motion

4.2 Standard Animations

/* Fade in */
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Slide in from bottom */
@keyframes slideInUp {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Slide in from right (toasts) */
@keyframes slideInRight {
  from {
    opacity: 0;
    transform: translateX(100%);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

/* Scale in (modals) */
@keyframes scaleIn {
  from {
    opacity: 0;
    transform: scale(0.95);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* Pulse (saving indicator) */
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

/* Spin (loading) */
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Shimmer (skeleton) */
@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

4.3 Reduced Motion

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

5. Responsive Breakpoints

/* Mobile first breakpoints */
/* sm: 640px - Large phones */
/* md: 768px - Tablets */
/* lg: 1024px - Laptops */
/* xl: 1280px - Desktops */
/* 2xl: 1536px - Large screens */

@media (min-width: 640px) {
  /* sm */
}

@media (min-width: 768px) {
  /* md */
}

@media (min-width: 1024px) {
  /* lg */
}

@media (min-width: 1280px) {
  /* xl */
}

@media (min-width: 1536px) {
  /* 2xl */
}

/* Mobile-only (no breakpoint, mobile first) */
/* styles apply to all, tablet+ override below */

/* Tablet and up */
@media (min-width: 768px) {
  .hide-tablet { display: none; }
}

/* Desktop only */
@media (min-width: 1024px) {
  .hide-desktop { display: none; }
}

6. Icon Guidelines

6.1 Icon System

Using Lucide Icons (SVG-based, MIT licensed).

<!-- Size variants -->
<svg class="icon icon-sm" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <!-- icon path -->
</svg>

<svg class="icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <!-- icon path -->
</svg>

<svg class="icon icon-lg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <!-- icon path -->
</svg>

6.2 Icon Usage

.icon {
  display: inline-block;
  vertical-align: middle;
  flex-shrink: 0;
}

.icon-sm { width: 1rem; height: 1rem; }
.icon-md { width: 1.25rem; height: 1.25rem; }
.icon-lg { width: 1.5rem; height: 1.5rem; }

/* Buttons with icons */
.btn-icon {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* Icon-only buttons need proper sizing */
.btn-icon-only {
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

6.3 Common Icons

Icon Use Case
search Search input
plus Create new items
folder-plus Create library
file-plus Create document
edit / pencil Edit
trash-2 Delete
download Export
chevron-down Dropdown toggle
chevron-right Tree expand
x Close, dismiss
check Confirm, saved
alert-circle Error
info Info toast
settings Settings
moon Dark mode
sun Light mode
home Dashboard
folder Library
file-text Document
tag Tag
link Internal link
external-link External link
copy Copy to clipboard
eye Preview
eye-off Hide
keyboard Shortcuts

7. Form Styling

7.1 Form Layouts

/* Vertical form (default) */
.form-vertical {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.form-horizontal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: flex-start;
}

.form-horizontal .form-group {
  flex: 1;
  min-width: 200px;
}

/* Inline form */
.form-inline {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}

7.2 Fieldset & Legend

fieldset {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}

legend {
  font-size: var(--text-sm);
  font-weight: var(--font-semibold);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
  padding: 0 var(--space-2);
}

7.3 Help Text & Errors

.help-text {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-top: var(--space-1);
}

.error-message {
  font-size: var(--text-sm);
  color: var(--color-danger);
  margin-top: var(--space-1);
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.input:invalid,
.input.error {
  border-color: var(--color-danger);
}

.input:invalid:focus,
.input.error:focus {
  box-shadow: 0 0 0 3px rgba(207, 74, 74, 0.25);
}

8. Status & State Colors

8.1 Status Badge Colors

Status Background Text Border
draft var(--color-warning-bg) var(--color-warning) none
approved var(--color-info-bg) var(--color-info) none
implemented var(--color-success-bg) var(--color-success) none

8.2 Priority Colors

Priority Color Emoji
high var(--color-priority-high) 🔴
medium var(--color-priority-medium) 🟡
low var(--color-priority-low) 🟢

8.3 Type Badge Colors

Type Border Text
requirement var(--color-requirement) var(--color-requirement)
note var(--color-note) var(--color-note)
spec var(--color-spec) var(--color-spec)
general var(--color-border) var(--color-text-muted)

9. Loading & Skeleton States

9.1 Skeleton Colors

.skeleton {
  background: linear-gradient(
    90deg,
    var(--color-border) 25%,
    var(--color-hover) 50%,
    var(--color-border) 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Dark mode adjustment */
[data-theme="dark"] .skeleton {
  background: linear-gradient(
    90deg,
    var(--color-surface) 25%,
    var(--color-surface-raised) 50%,
    var(--color-surface) 75%
  );
  background-size: 200% 100%;
}

9.2 Loading Overlay

.loading-overlay {
  position: absolute;
  inset: 0;
  background: rgba(var(--color-bg), 0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--z-sticky);
}

[data-theme="dark"] .loading-overlay {
  background: rgba(15, 17, 23, 0.8);
}

10. Dark Mode Implementation

10.1 Theme Toggle

// Theme toggle component
function ThemeToggle() {
  const toggle = () => {
    const current = document.documentElement.getAttribute('data-theme');
    const next = current === 'light' ? 'dark' : 'light';
    document.documentElement.setAttribute('data-theme', next);
    localStorage.setItem('theme', next);
  };

  return `
    <button class="btn-ghost" onclick="toggleTheme()" aria-label="Toggle theme">
      ${document.documentElement.getAttribute('data-theme') === 'dark' ? '☀️' : '🌙'}
    </button>
  `;
}

// Initialize theme on load
function initTheme() {
  const saved = localStorage.getItem('theme');
  const preferred = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
  const theme = saved || preferred;
  document.documentElement.setAttribute('data-theme', theme);
}

10.2 CSS Variables at :root vs [data-theme]

/* Default (dark) - no selector needed */
:root {
  --color-bg: #0f1117;
  /* ... */
}

/* Light theme - explicit selector */
[data-theme="light"] {
  --color-bg: #f5f6f8;
  /* ... */
}

11. Code Syntax Highlighting

Using highlight.js or Prism for code blocks within markdown.

/* Code highlighting tokens - dark theme */
.hljs {
  background: var(--color-surface);
  color: var(--color-text);
}

.hljs-keyword { color: #c678dd; }
.hljs-string { color: #98c379; }
.hljs-number { color: #d19a66; }
.hljs-function { color: #61afef; }
.hljs-comment { color: #5c6370; font-style: italic; }
.hljs-variable { color: #e06c75; }
.hljs-attr { color: #d19a66; }
.hljs-tag { color: #e06c75; }
.hljs-attribute { color: #d19a66; }

/* Code highlighting - light theme adjustments */
[data-theme="light"] .hljs-keyword { color: #a626a4; }
[data-theme="light"] .hljs-string { color: #50a14f; }
[data-theme="light"] .hljs-number { color: #986801; }
[data-theme="light"] .hljs-function { color: #4078f2; }
[data-theme="light"] .hljs-comment { color: #a0a1a7; }
[data-theme="light"] .hljs-variable { color: #e45649; }
[data-theme="light"] .hljs-attr { color: #986801; }
[data-theme="light"] .hljs-tag { color: #e45649; }
[data-theme="light"] .hljs-attribute { color: #986801; }

12. Print Styles

@media print {
  /* Hide navigation, sidebars, buttons */
  .sidebar,
  .app-header,
  .toolbar,
  .bottom-nav,
  .btn,
  .btn-ghost {
    display: none !important;
  }

  /* Expand content */
  .main-content {
    width: 100% !important;
    padding: 0 !important;
  }

  /* Adjust colors for print */
  body {
    background: white;
    color: black;
  }

  /* Ensure links show URLs */
  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.8em;
  }

  /* Avoid page breaks inside elements */
  .doc-card,
  .prose h1,
  .prose h2,
  .prose h3,
  .prose table {
    break-inside: avoid;
  }
}

13. Accessibility Checklist

  • All interactive elements have visible focus indicators
  • Color contrast ratio ≥ 4.5:1 for text
  • All images have alt text (or aria-hidden="true")
  • Form inputs have associated labels
  • Buttons have accessible names
  • Icons have aria-label or visible text
  • Modals trap focus and can be closed with Escape
  • Toast notifications use role="alert" or role="status"
  • Loading states announce to screen readers
  • Reduced motion is respected
  • Skip-to-content link is provided
  • Semantic HTML is used (nav, main, aside, article, etc.)