Skip to content

UI Style Guide

Ed Mozley edited this page Oct 18, 2026 · 1 revision

UI style guide

Status: first draft, from discussion #185. The action row is live in Tickets, Assets and Software, so it can be tried before every other module follows. Comments on the discussion are welcome. Once it has settled, the rest of the modules move over one family at a time (section 7).

The rules every analyst-facing screen in FreeITSM follows, so a screen behaves the same whichever module you are in. They sit alongside the Help page house style, Theming and dark mode and the mobile techniques.

The principle behind all of them: predictable beats clever. Somebody moving between twenty-odd modules should find the same thing in the same place, called the same word.


1. Buttons

  • One word, unless a second word is strictly necessary. Save, Delete, Cancel, New, Export, Scan - not Save changes, Add application or Export CSV.
  • Sentence case when there is more than one word: Delete forever, never Delete Forever.
  • No "+" and no icon on a text button. New, not + New. Icon-only buttons (search, filter, refresh in a list header) are fine, and they carry a tooltip.
  • The tooltip says what the one word does to what - New ticket, New asset. That is what a screen reader reads, so the short label loses nothing.

When two words are allowed:

Case Example Why
Two buttons with the same verb side by side New channel / New message One word would make them identical
A second, stronger version of an action Delete beside Delete forever The difference is the point
A label that names a place or a thing that changes Go to TICKET-123, Move to Junk The second word is the information
Right-click menu items Copy asset tag, Print label A menu is read, not scanned, and has room
An empty page's invitation Create your first article It is a sentence, not a button row

Classes: the shared .btn with .btn-primary or .btn-secondary (assets/css/inbox.css), or .action-row-btn in the action row. ⚠️ .btn-primary on its own sets only colour - always pair it with .btn. A module defining its own button family is a bug waiting to be found, and several still do (section 7).


2. Creating a record: the action row

Where: a strip directly under the module header, the full width of the page. What this page is on the left; what you can do here on the right, with New at the far right.

┌──────────────────────────────────────────────────────────────────────┐
│ ⋮⋮ Assets  [Assets][Table][Users][Dashboard]…        [Default▾] 🔔 A │  module header
├──────────────────────────────────────────────────────────────────────┤
│ Assets - 633 found                                            [New] │  action row
├──────────────┬───────────────────────────────────────────────────────┤
│ [search....] │                                                       │
  • New is one word, in the module's own colour (--accent), and its tooltip names the thing.
  • Every view of the same records gets the same row - list, table, board, calendar - so New is never missing just because you switched views. Where the form lives on another page, New links there (Assets' table view opens ./?new=1).
  • Outside every pane and panel. On a phone it stays on screen, never inside a Filters or Tags sheet.
  • A page with nothing to create has no New button, never a dead one. The row can still carry the title.
  • Shortcuts may stay where they are useful. A + at the top of a board column, which creates a task already in that status, is a shortcut beside New, not instead of it.
  • Things that are not "creating a record" keep their own words and places - Upload, Scan, Plan, Assign.

Building it: never by hand. Straight after the header include:

<?php include 'includes/header.php'; ?>
<?php require_once '../includes/action_row.php';
      renderActionRow(t('tickets.nav.inbox'), [
          actionRowNew('openNewTicketModal()', t('tickets.list.new_ticket_btn')),
      ]); ?>
Option Use
title_id The page rewrites the title - Assets' Assets - 633 found
count_id A count after the title, which the page fills; the dash appears only once it is filled
extra Trusted HTML before the buttons - Software's search box
actionRowNew($js, $tooltip, true) New is a link, not a script call

The row brings its own stylesheet (assets/css/action-row.css, through BASE_URL), so no page needs a new <link>. It also makes the page's .main-container (or .dt-page) take whatever height is left, so a page needs no calc(100vh - N) of its own.


3. Full width

  • Pages use the whole width of the screen. No max-width on a page's container, no centred column.
  • ⚠️ max-width: none is not enough inside a flex parent: an inherited margin: auto still shrinks and centres it. Use width: 100%; max-width: none; margin: 0.
  • Caps on single things are fine - a text input, a modal, a reading column inside a document viewer - because a form field stretched across 3000px helps nobody.
  • Check by measuring, never by reading the CSS: the container's getBoundingClientRect().width against the window.

4. Colour and theme

  • Theme tokens only - var(--surface), var(--text), var(--border), var(--accent) - never a hard-coded colour, so dark mode works. See Theming and dark mode.
  • Each module's accent comes from includes/module-colors.php through --accent. The primary button takes it.

5. Words

  • Sentence case for every heading, tab, label and button: Software inventory, Recycle bin.
  • Plain hyphens in text, and one name for one thing across modules.
  • Every string goes through t(). A new shared word belongs in lang/en/common.php.

6. Phones

Everything phone-specific lives inside @media (max-width: 768px), so the desktop never changes because of a phone fix. See Mobile-friendly techniques and the module's own mobile page before changing anything. A shared component may carry its own phone rules in its own stylesheet, gated the same way (action-row.css does).


7. Rollout

First: Tickets, Assets and Software (the three named in #185). Then, once the discussion is happy with them, one family at a time:

Family Modules Where New is today
✅ Done Tickets, Assets (incl. Table), Software The action row
Registers CMDB, Domains (incl. Table), Contracts Toolbar; toolbar; sidebar bottom (phone: footer bar)
Service work Changes (incl. Calendar, Table), Problems, Tasks (Board, List, Calendar, Timeline, Table) Sidebar below filters; sidebar top; per-column + only
Content Knowledge, Checklists, Process mapper, Forms, LMS, Workflow, Network mapper Sidebar below tags (phone: Tags sheet); sidebar top (phone: Filters sheet); sidebar; toolbars
Other Calendar (incl. Table), Projects, Service status, War room Sidebar top; toolbar; section titles; sidebar
No create People, Morning checks, Reporting, Watchtower, System Row for the title only, if at all

Also found by the audit, to fix as each module moves:

  • About 70 buttons with more than one word on main pages - Create Ticket, Export CSV, Paste list, Back to list, Full screen - and eight in Title Case.
  • About 15 different button classes for the one create action, and module-local .btn families in Assets, CMDB, Service status, Calendar and LMS.
  • "+" written three ways - in the translation, added in PHP, or as an icon - and none at all elsewhere.
  • Reporting's landing page is the one main page still capped (1160px).

Related pages

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally