#Visual Design Guidelines

This document is the authoritative reference for the kbpy web interface's visual design system. All templates should follow these rules. See also docs/guidelines/ux.md for terminology, personas, and information architecture.

#Typography

#Font Stack

Role Font Weights CSS Class CDN
Headings Montserrat 500, 600, 700 font-heading Google Fonts
Body Inter 400, 500, 600, 700 font-sans (default) Google Fonts
Monospace JetBrains Mono 400, 500, 600 font-mono Google Fonts

#Type Scale

Element Classes
Page title (h1) font-heading text-2xl font-bold text-gray-900
Section heading (h2) text-lg font-semibold text-gray-900
Subsection heading (h3) font-medium text-gray-900 or font-semibold text-gray-900
Body text text-sm text-gray-600
Description below header text-sm text-gray-600 mt-2 max-w-2xl
Stats number (index/detail) text-2xl font-heading font-bold
Stats number (dashboard only) text-3xl font-heading font-bold
Metadata/labels text-xs text-gray-500
Sidebar section headers text-[10px] font-semibold text-gray-400 uppercase tracking-wider

#When to Use Monospace

Use font-mono only for:

  • Code identifiers inside definition blocks (from_attribute, match_field)
  • Source file paths and line numbers
  • Code snippets (<pre><code>)
  • Entity IDs (like SVC_001) when they are technical identifiers

Do not use font-mono for:

  • Entity set names displayed to users (use | readable filter instead)
  • Relationship names, partition names, expectation names

#Quoting External Names

When externally-provided names (entity sets, partitions, relationships, expectations, attributes, attestation sources) appear inline in prose or sentences, wrap them in typographic single quotes (\u2018\u2026\u2019):

  • Do: Entity in \u2018all employees\u2019 where \u2018salary\u2019 is greater than 100000
  • Don't: Entity in all employees where salary is greater than 100000

Use the | readable_quoted Jinja2 filter for prose contexts. Use the plain | readable filter for headings, standalone link labels, badges, and table cells where quotes are not needed.

Names generated by _readable_name() in theory.py are automatically quoted for use in description sentences.

#Education Text

Education text paragraphs (the text-sm text-gray-600 descriptions below page headers) explain domain concepts to users. When highlighting kbpy-specific terms in education text:

  • Do use <strong> to emphasise domain-specific terms (e.g. <strong>expector</strong>, <strong>is_true</strong>, <strong>aligned</strong>)
  • Do not use semantic colors (text-known-true, text-void, etc.) for terms in education text — colors are reserved for data values and status indicators, not prose
  • Do use <em> for rhetorical questions or emphasis that isn't a term definition

#Color System

#Accent Color

Token Hex Usage
accent #06B6D4 Links, active sidebar items, interactive highlights
accent-hover #0891B2 Hover state for accent-colored elements

#Semantic Data Colors

These represent the four possible states of a data value or evaluation result.

Token Hex Tailwind Meaning
known-true #10B981 emerald-500 True value, met expectation, aligned
known-false #6B7280 gray-500 False value (neutral, not an error)
unknown #F59E0B amber-500 Missing data, uncertain status
void #EF4444 red-500 Not applicable, not met, misaligned

These are defined in both the Tailwind config (for JIT-compiled classes) and as explicit CSS rules (for classes that Tailwind CDN cannot generate dynamically). Both definitions are required.

#Type Badge Colors

Each domain concept type has a consistent badge color:

Concept Color Example
Entity Set emerald bg-emerald-100 text-emerald-800
Partition yellow bg-yellow-100 text-yellow-800
Expectation pink bg-pink-100 text-pink-800
Relationship teal bg-teal-100 text-teal-800
Entity Type purple bg-purple-100 text-purple-800
Entity blue bg-blue-100 text-blue-800
Attestation Source cyan bg-cyan-100 text-cyan-800
Expector indigo bg-indigo-100 text-indigo-800
Expectee orange bg-orange-100 text-orange-800
Question amber bg-amber-100 text-amber-800

These are defined in partials/badge.html via the type_badge macro. Always use the macro rather than hardcoding badge colors in templates.

#Status Badge Colors

Status Background Text
Met bg-green-100 text-green-800
Not met bg-red-100 text-red-800
Uncertain bg-amber-100 text-amber-800

#Gray Scale Usage

Limit gray usage to these tones for consistent hierarchy:

Gray Role
gray-50 Subtle backgrounds (table headers, sidebar)
gray-100 Hover backgrounds, code backgrounds
gray-200 Borders, dividers
gray-400 Sidebar section headers, separators, secondary counts
gray-500 Metadata text, labels
gray-600 Body text, descriptions
gray-700 Secondary headings, emphasized body
gray-900 Primary headings, strong text

#Layout

#Content Width

Context Class When
Standard module pages max-w-4xl Most pages
Pages with tables or wide content max-w-5xl Entity sets index, campaign dashboard, actions index
Graph or source code viewer max-w-6xl Domain model, source file viewer
Standalone pages (no sidebar) max-w-4xl mx-auto Domain overview
Dashboard landing max-w-3xl mx-auto Home page

Content in sidebar pages (extending module_base.html) must not use mx-auto. The content left-aligns against the sidebar edge. Centering creates misalignment with the sidebar navigation.

#Spacing

Element Spacing
Page header to content mb-6
Between major sections mb-8
Description text max width max-w-2xl
Section heading to content mb-4
Card internal padding p-4 (standard), p-5 (dashboard stats), p-6 (wide cards)

#Components

#Cards

Standard card: bg-white border border-gray-200 rounded-lg p-4

Interactive card (clickable): Add hover:border-accent hover:shadow-sm transition-all cursor-pointer

Status-bordered card: Add border-l-4 border-l-{color}-500 for left accent stripe, or border-2 border-{color}-500 for full status border.

#Stats Cards

Index pages (Pattern B): bg-white border border-gray-200 rounded-lg p-4 text-center

  • Number: text-2xl font-heading font-bold text-{color}
  • Label: text-sm text-gray-500

Detail pages with semantic meaning (Pattern C): Add border-l-4 border-l-{color}-500

Dashboard navigational stats (Pattern A): bg-gray-50 rounded-lg p-5 hover:bg-gray-100 transition-colors

  • Number: text-3xl font-heading font-bold text-gray-900

#Tables

Container: bg-white border border-gray-200 rounded-lg overflow-hidden
Header:    bg-gray-50
Header cell: px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider
Body:      divide-y divide-gray-200
Row:       hover:bg-gray-50 (add cursor-pointer if clickable)
Cell:      px-4 py-3 text-sm

Always include tracking-wider on table header cells.

#Badges

Use the macros from partials/badge.html:

  • type_badge(type) — for domain concept type labels
  • status_badge(status) — for met/not_met/uncertain
  • exhaustive_badge(is_exhaustive) — for exhaustive/partial
  • value_badge(status, display, reason) — for data values

#Buttons

Standard button: inline-flex items-center px-4 py-2 text-sm text-gray-700 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors

Compact button (toolbars): px-3 py-2 instead of px-4 py-2

Small button (source view): px-2.5 py-1 text-xs text-gray-600 bg-gray-100 rounded hover:bg-gray-200 hover:text-gray-800

Primary CTA (rare): px-4 py-2 text-sm text-white bg-accent rounded-lg hover:bg-accent-hover

All buttons must include transition-colors.

<div class="mt-8">
    <a href="..." class="inline-flex items-center px-4 py-2 text-sm text-gray-700 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
        &larr; Back to ...
    </a>
</div>

#Empty States

Use the empty_state macro from partials/empty_state.html:

bg-gray-50 border border-gray-200 rounded-lg p-8 text-center

#Page Headers

Standard pattern for all module pages:

<div class="mb-6">
    <div class="flex items-center space-x-3 mb-2">
        {{ type_badge("concept_type") }}
        <h1 class="font-heading text-2xl font-bold text-gray-900">Title</h1>
    </div>
    <p class="text-sm text-gray-500">Metadata</p>
    <p class="text-sm text-gray-600 mt-2 max-w-2xl">Description paragraph.</p>
</div>

#Interactivity

#Hover States

All interactive elements must have visible hover feedback:

  • Links: hover:underline and/or hover:text-accent
  • Cards: hover:border-accent hover:shadow-sm transition-all
  • Buttons: hover:bg-gray-50 transition-colors or hover:bg-gray-100 transition-colors
  • Table rows: hover:bg-gray-50

#Transitions

All elements that change appearance on hover must include a transition:

  • Color changes: transition-colors
  • Multiple property changes (border + shadow): transition-all

#Focus States

All interactive elements must be keyboard-accessible with visible focus indicators:

a:focus-visible, button:focus-visible, [onclick]:focus-visible, select:focus-visible {
    outline: 2px solid #06B6D4;
    outline-offset: 2px;
}

Elements with onclick handlers must have tabindex="0" to be keyboard-reachable, and must respond to Enter/Space keypress.

#Accessibility

#Emoji

Emoji used as decorative icons must be wrapped in <span aria-hidden="true"> to prevent screen readers from announcing them:

<span aria-hidden="true" class="mr-2">📋</span> Expectations

#Status Communication

Status should be communicated through multiple channels (not color alone):

  • Color + text label (badges show "met", "not met", "uncertain")
  • Color + icon (value badges show ✓, ✗, ❓, ⚠️ alongside color)
  • Reason chains provide textual explanation

#Semantic HTML

  • Use <nav> for navigation regions
  • Use <section> for distinct content sections
  • Use <table> with proper <thead>, <th scope="col"> for tabular data
  • Use heading hierarchy correctly (h1 → h2 → h3, never skip levels)

#Border Radius

Element Radius
Cards, containers, tables rounded-lg (8px)
Badges rounded (4px)
Dropdowns rounded-md (6px)
Progress bars rounded-full
Buttons rounded-lg (8px)

#Shadows

Shadows are used sparingly for interactive feedback only:

  • hover:shadow-sm on interactive cards
  • hover:shadow-md on domain overview campaign cards
  • No shadows on static elements