diff --git a/web/README.md b/web/README.md index d8127f96e0..1e533834ff 100644 --- a/web/README.md +++ b/web/README.md @@ -46,3 +46,43 @@ src/ ├── main.tsx # React entry point └── index.css # Tailwind imports and theme variables ``` + +## Typography & contrast rules + +Read before adding or editing UI styles. These rules keep the dashboard legible across all built-in themes and stop drift back into the patterns the design system was just refactored out of. + +### Text size floor + +- **Minimum body size: `text-xs` (12px / 0.75rem).** Do not use arbitrary `text-[0.6rem]`, `text-[0.65rem]`, `text-[9px]`, `text-[10px]`, or `text-[11px]` on copy, hints, labels, counts, or badges. Use the standard scale: `text-xs`, `text-sm`, `text-base`. +- Smaller sizes are only acceptable on **decorative overlays** (chart stripes, empty-state icons) — never on text the user is meant to read. + +### Opacity floor on text + +- **Never apply opacity below 0.7 to text.** No `opacity-30`, `opacity-50`, `opacity-60` on ``s, `

`s, labels, etc. +- **Do not stack opacity tokens.** Patterns like `text-muted-foreground/60`, `text-midground/70`, `text-foreground/50` create unpredictable WCAG failures because the parent token already has alpha. +- Use the **semantic text tokens** from `@nous-research/ui`'s `globals.css`: + - `text-text-primary` — default body text. + - `text-text-secondary` — subtitles, meta, inactive nav. + - `text-text-tertiary` — small chrome labels, counts, footnotes. + - `text-text-disabled` — disabled states. + - `text-text-on-accent` — text on filled accent surfaces. + +### Brand uppercase via `text-display`, never `uppercase` + +- The dashboard preserves the Nous brand uppercase aesthetic, but it is **opt-in per element, not global**. +- Apply uppercase via the DS utility `text-display` on **brand chrome only** — page titles, nav section headings, badges, brand wordmark. DS components (`Button`, `Badge`, `Tabs`, `Segmented`, etc.) already self-apply `text-display`. +- **Do not** add `uppercase` (the literal Tailwind class) anywhere in `hermes-agent/web/src`. +- **Do not** add `normal-case` to "opt out" of inheritance — the app shell no longer forces uppercase, so opt-outs aren't needed. The only legitimate `normal-case` overrides are on DS ` {t.app.brand} @@ -498,7 +498,7 @@ export default function App() { Hermes @@ -512,7 +512,7 @@ export default function App() { size="icon" onClick={closeMobile} aria-label={t.app.closeNavigation} - className="lg:hidden text-midground/70 hover:text-midground" + className="lg:hidden text-text-secondary hover:text-midground" > @@ -542,7 +542,7 @@ export default function App() { @@ -671,10 +671,10 @@ function SidebarNavLink({ closeMobile, item, t }: SidebarNavLinkProps) { cn( "group relative flex items-center gap-3", "px-5 py-2.5", - "font-mondwest text-[0.8rem] tracking-[0.12em]", + "font-mondwest text-display text-sm tracking-[0.12em]", "whitespace-nowrap transition-colors cursor-pointer", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-midground", - isActive ? "text-midground" : "opacity-60 hover:opacity-100", + isActive ? "text-midground" : "text-text-secondary hover:text-midground", ) } style={{ @@ -746,7 +746,7 @@ function SidebarSystemActions({ onNavigate }: { onNavigate: () => void }) { {t.app.system} @@ -772,12 +772,12 @@ function SidebarSystemActions({ onNavigate }: { onNavigate: () => void }) { active={busy} className={cn( "gap-3 px-5 py-1.5 whitespace-nowrap", - "font-mondwest text-[0.75rem] tracking-[0.1em]", - "transition-opacity", + "font-mondwest text-display text-xs tracking-[0.1em]", + "transition-colors", busy - ? "text-midground opacity-100" - : "opacity-60 hover:opacity-100", - "disabled:opacity-30", + ? "text-midground" + : "text-text-secondary hover:text-midground", + "disabled:text-text-disabled", )} > {isPending ? ( diff --git a/web/src/components/AutoField.tsx b/web/src/components/AutoField.tsx index 0f96d42042..4e3451c10f 100644 --- a/web/src/components/AutoField.tsx +++ b/web/src/components/AutoField.tsx @@ -11,8 +11,8 @@ function FieldHint({ schema, schemaKey }: { schema: Record; sch return (

- {keyPath && {keyPath}} - {description && {description}} + {keyPath && {keyPath}} + {description && {description}}
); } diff --git a/web/src/components/BottomPickSheet.tsx b/web/src/components/BottomPickSheet.tsx index 1490f4090c..459857eada 100644 --- a/web/src/components/BottomPickSheet.tsx +++ b/web/src/components/BottomPickSheet.tsx @@ -200,7 +200,7 @@ export function BottomPickSheet({ {title} diff --git a/web/src/components/ChatSidebar.tsx b/web/src/components/ChatSidebar.tsx index c311673faf..a115d887ec 100644 --- a/web/src/components/ChatSidebar.tsx +++ b/web/src/components/ChatSidebar.tsx @@ -304,13 +304,13 @@ export function ChatSidebar({ channel, className }: ChatSidebarProps) { return (