# Frontend Design — Rules & Guidelines Rules and conventions every designer must follow when working on BanGUI. This document defines the visual language, component usage, and design principles for the entire application. Read this before creating your first mockup or writing your first style. **Design system:** [Microsoft Fluent UI](https://developer.microsoft.com/en-us/fluentui#/get-started/web) --- ## 1. Design Philosophy - **Clarity over decoration.** Every visual element must serve a purpose. If it does not help the user read, navigate, or act — remove it. - **Consistency over creativity.** Reuse existing Fluent UI components and patterns before inventing new ones. A familiar interface lowers cognitive load. - **Content first.** BanGUI is a data-heavy monitoring tool. Design around the data — tables, charts, status indicators — not around ornamental chrome. - **Quiet until necessary.** The default state of the UI should feel calm and neutral. Reserve colour, motion, and elevation for moments that genuinely need attention (errors, warnings, active bans). - **Accessible by default.** Every design decision must pass WCAG 2.1 AA. Colour alone must never be the only way to convey information. --- ## 2. Theming & Colour All colour decisions go through the Fluent UI theme system. Never hard-code hex values in components. ### Theme Slots Use **semantic colour slots** from `ISemanticColors` wherever possible. Semantic slots describe *intent* rather than *appearance*, which means the UI adapts automatically when the theme changes. | Intent | Semantic slot | Example usage | |---|---|---| | Primary action | `themePrimary` | Primary buttons, active nav items, links | | Danger / error | `errorText`, `errorIcon` | Ban warnings, failed connections, validation errors | | Success | `successIcon` (extend with custom slot) | Successful unban confirmation, server online status | | Warning | Shared colour `yellow` / `yellowDark` | Blocklist import warnings, approaching limits | | Neutral text | `neutralPrimary` | Body copy, table cell text | | Secondary text | `neutralSecondary` | Metadata, timestamps, captions | | Disabled | `disabledText`, `disabledBackground` | Inactive controls, unavailable jails | | Surface | `bodyBackground`, `bodyStandoutBackground` | Page canvas, sidebar, card backgrounds | ### Custom Theme BanGUI uses a **single custom theme** generated with the [Fluent UI Theme Designer](https://aka.ms/themedesigner). The theme is defined once in code and consumed everywhere via `ThemeProvider`. - The primary colour must have a **contrast ratio of at least 4.5 : 1** against `white` for text and **3 : 1** for large text and UI elements. - Provide a **dark theme variant** alongside the default light theme. Both must share the same semantic slot names — only the palette values differ. - Never reference Fluent UI palette slots (`themeDarker`, `neutralLight`, etc.) directly in components. Always go through semantic slots so theme switching works seamlessly. ### Colour Rules - **Never use inline hex or RGB values.** Always reference theme tokens (`theme.palette.*` or `theme.semanticColors.*`). - Use the **9-step theme colour ramp** (`themeLighterAlt` through `themeDarker`) for subtle tints and shades within the primary hue. - Use the **14-step neutral ramp** (`white` through `black`) for backgrounds, borders, and text hierarchy. - Reserve the **shared accent colours** (red, yellow, green, blue) strictly for semantic meaning: errors, warnings, success, informational. Never use them decoratively. - When two adjacent regions need separation, prefer a **1 px `neutralLight` border** or a subtle background shift (`bodyStandoutBackground`) over a heavy line. --- ## 3. Typography Use the Fluent UI type ramp exclusively. Do not introduce custom font sizes. ### Type Ramp | Token | Size | Weight | Usage in BanGUI | |---|---|---|---| | `FontSizes.size28` | 28 px | Semibold (600) | Page titles — "Ban Overview", "Jail Management" | | `FontSizes.size20` | 20 px | Semibold (600) | Section headers — "Ban List", "Server Settings" | | `FontSizes.size16` | 16 px | Semibold (600) | Card titles, panel headers, sub-section labels | | `FontSizes.size14` | 14 px | Regular (400) | Body text, table cell content, form labels, buttons | | `FontSizes.size12` | 12 px | Regular (400) | Metadata: timestamps, IP geolocation tags, ban counts, tooltips | | `FontSizes.size10` | 10 px | Regular (400) | Badges, disclaimer text, chart axis labels (use sparingly) | ### Typography Rules - **Font family** — Use the Fluent default (`Segoe UI`, with the standard fallback stack). Never override the font family unless a monospace context demands it (log output, regex patterns, IP addresses — use `Consolas, "Courier New", monospace` for those). - **Weight** — Only two weights in regular UI: **Regular (400)** for body text and **Semibold (600)** for headings and emphasis. Use **Bold (700)** only in exceptional data visualisation contexts. - **Line height** — Follow the recommended line heights from the Fluent type ramp (e.g., 14 px text → 20 px line-height, 20 px text → 28 px line-height). - **Text colour hierarchy** — Use `neutralPrimary` for primary text, `neutralSecondary` for secondary/supporting text, and `neutralTertiary` only for placeholder text. - **Alignment** — Left-align all text by default. Centre-align only short labels inside cards or stat badges. Never right-align body text. - **Truncation** — Long text (IP addresses, log paths, regex patterns) must truncate with an ellipsis and show the full content in a tooltip on hover. - **Monospace contexts** — IP addresses, regex patterns, log paths, code snippets, and jail filter expressions must always render in monospace for readability. --- ## 4. Layout & Spacing ### Grid - Use a **12-column responsive grid**. In Fluent UI React, prefer CSS Grid or `Stack` for layout rather than Fabric Core grid classes. - The main content area sits next to a **fixed-width side navigation** (240 px collapsed to 48 px on small screens). - Maximum content width: **1440 px**, centred on ultra-wide monitors. ### Breakpoints Follow the Fluent UI breakpoint scale: | Name | Range | Layout behaviour | |---|---|---| | Small | 320 – 479 px | Single column, collapsed nav, stacked cards | | Medium | 480 – 639 px | Single column, optional side nav overlay | | Large | 640 – 1023 px | Two columns, side nav visible | | Extra large | 1024 – 1365 px | Full layout, side nav + main + optional aside | | XX-large | 1366 – 1919 px | Comfortable full layout | | XXX-large | 1920 px + | Max-width content, extra breathing room | ### Spacing Scale Use the Fluent **4 px base unit** for all spacing. Common values: | Token | Value | Usage | |---|---|---| | `s2` | 4 px | Inline spacing between icon and label | | `s1` | 8 px | Padding inside compact elements (badges, chips) | | `m` | 16 px | Standard padding inside cards and panels | | `l1` | 20 px | Gap between table rows / list items | | `l2` | 32 px | Margin between sections on a page | | `xl` | 48 px | Top margin for page content below the header | ### Spacing Rules - **Never use arbitrary pixel values.** Always snap to the 4 px grid (4, 8, 12, 16, 20, 24, 32, 40, 48…). - Padding inside a card or panel: **16 px** on all sides. - Gap between cards in a grid: **16 px**. - Gap between a page title and the first content block: **24 px**. - Table cell padding: **12 px horizontal**, **8 px vertical**. --- ## 5. Elevation & Depth Use Fluent UI depth levels to communicate layering. Do not create custom `box-shadow` values. | Level | Token | Usage in BanGUI | |---|---|---| | **Depth 4** | `Depths.depth4` | Cards, grid tiles, stat summaries on the dashboard | | **Depth 8** | `Depths.depth8` | Command bars, dropdown menus, filter popups | | **Depth 16** | `Depths.depth16` | Tooltips, hover cards (IP info popup), teaching callouts | | **Depth 64** | `Depths.depth64` | Modal dialogs (ban/unban confirmation), side panels | ### Elevation Rules - A surface's depth must **match its semantic importance**. A card sits at depth 4; a dialog demanding user action sits at depth 64. - The **page canvas has zero elevation** — it is the baseline. - Only **one modal level** may be visible at a time. Never stack dialogs. - When a dropdown or popup opens, the rest of the interface should receive an **overlay scrim** (`rgba(0, 0, 0, 0.4)`) only for depth-64 surfaces (modals). Lighter popups (depth 8–16) dismiss on outside click without a scrim. --- ## 6. Motion & Animation All animations follow Fluent motion principles. Motion should feel natural, purposeful, and quick. ### Timing | Duration | Token | Usage | |---|---|---| | 100 ms | `MotionDurations.duration1` | Micro-interactions: button press, checkbox toggle | | 200 ms | `MotionDurations.duration2` | Fade in/out of tooltips, small element transitions | | 300 ms | `MotionDurations.duration3` | Panel slide-in, page transitions, list reordering | | 400 ms | `MotionDurations.duration4` | Large surface entrance (full-screen modals, first-load hero) | ### Easing | Curve | Token | When to use | |---|---|---| | Decelerate | `MotionTimings.decelerate` | Elements entering the view (slide in, fade in) | | Accelerate | `MotionTimings.accelerate` | Elements leaving the view (slide out, fade out) | | Standard | `MotionTimings.standard` | Elements that reposition within the view | | Linear | `MotionTimings.linear` | Opacity-only transitions (fade out before a drill-in) | ### Animation Patterns | Interaction | Pattern | Details | |---|---|---| | Opening a jail detail panel | **Slide right in** | Panel slides from right edge, content fades in after 100 ms delay | | Banning / unbanning an IP | **Delete & Slide** | Row fades out (300 ms decelerate), remaining rows slide up (300 ms) | | Switching dashboard tabs | **Tabs & Pivots** | Active indicator slides (300 ms), outgoing content slides out, incoming slides in with 100 ms delay | | Navigating to a sub-page | **Drill In** | Old content fades out (100 ms linear), new content scales down in (300 ms decelerate) | ### Motion Rules - **Never animate something the user did not trigger.** Auto-refreshing data should appear without animation — just update in place. - **Respect `prefers-reduced-motion`.** When the OS or browser signals reduced motion, disable all non-essential animations and replace them with instant state changes. - **No loading spinners longer than 1 second without explanation.** If a network call exceeds 1 s, show a skeleton screen or a contextual loading message, not just a spinner. - Keep looping animations (pulsing status dots, progress bars) subtle and small-area. Never animate large surfaces continuously. --- ## 7. Iconography - Use the **Fluent UI icon set** (`@fluentui/react-icons` or the Fabric Core icon font). Do not introduce third-party icon libraries. - Icon size follows the adjacent text: **16 px** icons next to 14 px body text, **20 px** icons next to 16–20 px headers. - Icons must always have a **text label** or an `aria-label`. Icon-only buttons are acceptable only in toolbars and compact table rows, and they must have a tooltip. - Use **outline** icon variants by default. Switch to **filled** only for active or selected states (e.g., filled star for a favourited jail). - Icon colour inherits from the text colour of its context. Override only for semantic purposes (red icon for errors, green for success). --- ## 8. Component Usage Use Fluent UI React components as the building blocks. The following mapping shows which component to use for each BanGUI feature area. ### Navigation | Element | Fluent component | Notes | |---|---|---| | Side navigation | `Nav` | Persistent on large screens, collapsible on small. Groups: Dashboard, Map, Jails, Config, History, Blocklists. | | Breadcrumbs | `Breadcrumb` | Show on detail pages (Jail > sshd, History > IP detail). | | Page tabs | `Pivot` | Dashboard (Ban List / Access List), Map (Map / Access List). | ### Data Display | Element | Fluent component | Notes | |---|---|---| | Data tables | `DetailsList` | All ban tables, jail overviews, history tables. Enable column sorting, selection, and shimmer loading. | | Stat cards | `DocumentCard` or custom `Stack` card | Dashboard status bar — server status, total bans, active jails. Use `Depth 4`. | | Status indicators | `Badge` / `Icon` + colour | Server online/offline, jail running/stopped/idle. | | Country labels | Monospaced text + flag emoji or icon | Geo data next to IP addresses. | ### Forms & Actions | Element | Fluent component | Notes | |---|---|---| | Primary actions | `PrimaryButton` | "Ban IP", "Save Configuration", "Run Import Now". | | Secondary actions | `DefaultButton` | "Cancel", "Reset", "Clear Filters". | | Danger actions | `PrimaryButton` with danger styling | "Unban All", "Flush Chain". Red theme override on `themePrimary`. | | Text inputs | `TextField` | IP address entry, regex pattern input, search fields. | | Dropdowns | `Dropdown` | Jail selector, time-range presets, log level picker. | | Toggles | `Toggle` | Enable/disable jail, enable blocklist source, ignore-self toggle. | | Confirmations | `Dialog` | Confirm ban, confirm unban-all, confirm delete actions. Always require explicit user action. | ### Feedback | Element | Fluent component | Notes | |---|---|---| | Success messages | `MessageBar` (success) | "IP 1.2.3.4 has been banned in jail sshd." | | Error messages | `MessageBar` (error) | "Failed to connect to fail2ban server." | | Warning messages | `MessageBar` (warning) | "Blocklist import encountered 12 invalid entries." | | Loading states | `Shimmer` | Apply to `DetailsList` rows and stat cards while data loads. | | Empty states | Custom illustration + text | "No bans recorded in the last 24 hours." Centre on the content area. | | Tooltips | `Tooltip` / `TooltipHost` | Full IP info on hover, full regex on truncated text, icon-only button labels. | --- ## 9. Tables & Data Grids Tables are the primary UI element in BanGUI. They must be treated with extreme care. - Use Fluent UI's `DetailsList` for all tabular data. - **Column widths:** Give the widest anticipated content enough room. IP addresses need ~140 px, timestamps ~180 px, country codes ~80 px. Use `minWidth` and `maxWidth` on each column. - **Row density:** Use `compact` mode for tables with many rows (ban lists, history) and `normal` mode for tables with fewer rows (jail overview, blocklist sources). - **Sorting:** Every column with comparable data must be sortable. Default sort: newest first for time-based tables. - **Selection:** Table rows for actionable entities (banned IPs, jails) must support single selection that highlights the row and enables contextual actions in a command bar above. - **Shimmer loading:** When data is loading, show shimmer placeholder rows matching the expected column layout. Never show a blank table body. - **Empty state:** When the table has zero rows, display a centred message and icon inside the table frame (e.g., "No bans in this time range"). - **Sticky header:** Table headers must stick to the top of the scrollable area so column labels are always visible. - **Zebra striping:** Do not use alternating row colours. Fluent UI relies on hover and selection highlights instead. - **Pagination vs. virtual scrolling:** For tables that may exceed 100 rows, use virtual scrolling (`DetailsList` supports this natively). Avoid traditional pagination. --- ## 10. Cards & Stat Blocks The dashboard uses cards to display key figures (server status, total bans, active jails). - Each card is a contained surface at **Depth 4** with **16 px padding** on all sides. - Card layout: **icon or status dot** (left) + **large numeric value** (`FontSizes.size28`, semibold) + **label** (`FontSizes.size12`, `neutralSecondary`). - Cards arrange in a horizontal row using `Stack` with `horizontal` tokens and `16 px` gap. - On small screens, cards stack vertically in a single column. - **Do not put actions inside stat cards.** They are read-only summaries. Actions belong in context menus or command bars. --- ## 11. World Map View - The map renders country outlines only — **no fill colours, no satellite imagery, no terrain shading**. - Countries with banned IPs display a **count badge** centred inside the country polygon. Use `FontSizes.size14` semibold, `themePrimary` colour. - Countries with zero bans remain completely blank — no label, no tint. - On hover: country region gets a subtle `neutralLighterAlt` fill. On click: fill shifts to `themeLighterAlt` and the companion table below filters to that country. - The map must have a **light neutral border** (`neutralLight`) around its container, at **Depth 4**. - Time-range selector above the map uses `Pivot` with quick presets (24 h, 7 d, 30 d, 365 d). --- ## 12. Forms & Inputs - Use `TextField` with a **visible label above** the input (not a floating label). - Every text input must show a `description` or `placeholder` that illustrates the expected format (e.g., `192.168.1.0/24`). - Validation errors appear **below the input** using the built-in `errorMessage` prop — red text, small size (`FontSizes.size12`). - Group related fields inside a bordered `Stack` or a `GroupedList` with a section header. - Action buttons sit at the **bottom-right** of a form group, with primary on the right and secondary (Cancel) on the left. - For the Regex Tester: use a side-by-side layout — sample log line on the left, regex input on the right, match result highlighted below. Monospace font for both fields. --- ## 13. Feedback & Status ### Server Status Bar - A thin horizontal bar at the very top of the content area (below the nav header). - **Online:** green dot + "fail2ban running — v0.11.2 — 14 jails — 231 total bans". Use `FontSizes.size12`, `neutralSecondary`. - **Offline / unreachable:** red dot + "fail2ban server unreachable" in `errorText` colour. Bar background shifts to a very light red tint. - This bar is **non-dismissible** and always visible. ### Inline Feedback - After a successful action (ban, unban, save config): show a `MessageBar` of type `success` **at the top of the current content area**, auto-dismiss after 5 seconds. - After a failed action: show a `MessageBar` of type `error`, **not auto-dismissed** — the user must close it manually. - Warnings (import issues, validation hints) use `MessageBar` of type `warning`, not auto-dismissed. --- ## 14. Dark Theme - Every screen must look correct in both light and dark themes. - Design light theme first, then verify dark theme by swapping the palette in the Theme Designer. - **Never assume white backgrounds.** Always use `bodyBackground` or `bodyStandoutBackground` from the theme. - **Never use black text directly.** Always use `neutralPrimary`, which adapts to both themes. - Images, illustrations, and the world map must have **transparent or theme-aware backgrounds** that do not create harsh rectangles in dark mode. - Test contrast ratios in **both themes** — a colour that passes AA on white may fail on dark grey. --- ## 15. Accessibility - **Colour contrast:** All text must meet WCAG 2.1 AA minimums (4.5 : 1 for normal text, 3 : 1 for large text and UI components). Use the [Fluent UI colour accessibility guide](https://res-1.cdn.office.net/files/fabric-cdn-prod_20230815.002/fabric-website/files/coloraccessibility_29sep2016.pdf) as reference. - **Keyboard navigation:** Every interactive element must be reachable via Tab and operable with Enter or Space. Sidebar navigation, table row selection, dropdown menus — all must work without a mouse. - **Focus indicators:** Use the default Fluent UI focus ring (2 px `themePrimary` outline). Never hide or override focus styles. - **Screen readers:** Every icon-only button has an `aria-label`. Tables use `aria-sort` on sortable columns. Status indicators have `aria-live="polite"` regions so screen readers announce changes. - **Reduced motion:** Wrap all custom animations in a `prefers-reduced-motion` media query and provide an instant fallback. - **Touch targets:** All interactive elements have a minimum tap target of **44 x 44 px** on touch devices (achieved via padding if the visible element is smaller). --- ## 16. Design Tokens & File Organisation - All theme definitions live in a single file: `theme/appTheme.ts`. - All spacing, sizing, and z-index constants live in `theme/tokens.ts`. - Components consume tokens via the theme context — never import raw pixel values. - When a design requires a value not already in the token set, **add it to the shared tokens file** and document its purpose. Never define a one-off constant inside a component. ```ts // theme/appTheme.ts import { createTheme } from "@fluentui/react"; export const lightTheme = createTheme({ palette: { themePrimary: "#0078d4", themeDarkAlt: "#106ebe", // ... generated via Theme Designer }, }); export const darkTheme = createTheme({ palette: { themePrimary: "#2b88d8", themeDarkAlt: "#3aa0f3", // ... }, }); ``` ```ts // theme/tokens.ts export const spacing = { xs: 4, s: 8, m: 16, l: 24, xl: 32, xxl: 48, } as const; export const contentMaxWidth = 1440; export const sideNavWidth = 240; export const sideNavCollapsedWidth = 48; ``` --- ## 17. Do-Not-Do List | Do | Do Not | |---|---| | Use Fluent UI components for every standard control | Build custom buttons, dropdowns, or modals from scratch | | Reference theme tokens for all colours | Hard-code hex values like `#ff0000` in components | | Follow the 4 px spacing grid | Use arbitrary pixel values (13 px, 7 px, 19 px) | | Provide a tooltip for every icon-only button | Leave icons unlabelled and inaccessible | | Use `Shimmer` for loading states | Show a blank screen or a standalone spinner with no context | | Design for both light and dark themes | Default to white backgrounds assuming light mode only | | Use `DetailsList` for all tabular data | Use raw HTML `