Skip to content

Help Page House Style

Ed Mozley edited this page Aug 6, 2026 · 1 revision

Help Page House Style

Every module in FreeITSM has an in-app guide β€” 26 pages in all, from tickets/help.php down to workflow/help-ssl.php. They all look the same because they all use one stylesheet: assets/css/help.css.

This page is the rule they follow. Read it before writing a new guide, and before adding anything to an existing one.


Why this exists

Until August 2026 there was no house style. There were 26 copies of one.

Each guide carried its own <style> block β€” the same ~280 lines of layout under its own prefix: cal-help-*, ct-help-*, kb-help-*, tk-help-*, wt-help-*, and a dozen more. Every copy was individually reasonable. The problem was that changing one never changed the others, so they drifted:

  • The CMDB guide tinted sections 2, 4 and 8 with a grey band, for no rule anyone could state β€” and those sections bled 48px wider than their neighbours, so "level 1" appeared to be a different width depending where you were on the page.
  • .cmdb-help-section > p had no indent at all, so three paragraphs sat at level 1 while the steps and cards beside them were indented. Three others had been patched by hand with an inline style="margin-left:46px".
  • Twelve pages hand-coloured their feature icons blue / green / orange / purple down a single grid.
  • Seven different class names β€” tip, callout, warn, gotcha, danger, consequence, example β€” did the job of one.
  • Most pages simply display: noned their contents list below 900px.

7,657 lines of duplicated styling became 600. The rest of this page is what replaced it.


1. The heading model

This is the rule. Everything else is detail.

level 1   .help-section              full width of the pane, NEVER indented
          .help-section-header         (number) + <h3> + lead <p>
level 2   everything else in it      indented to --help-indent
level 3   .help-subsection           its <h4> sits at level 2,
                                     its content one step further in

A guide reads as a numbered list of sections. Each section spans the full width; everything belonging to that section lines up underneath its heading. That holds at every screen size β€” on a phone the indent shrinks from 46px to 32px along with the badge that sets it, rather than collapsing to the left edge.

πŸ”‘ The indent is on the parent, not the children

The old pages put margin-left: 46px on each individual component, which is exactly why the occasional paragraph fell out of line β€” somebody added a <p> and there was nothing to remind them. The house style does it the other way round:

.help-section {
    padding-left: var(--help-indent);      /* everything inside starts here */
}
.help-section-header {
    margin-left: calc(var(--help-indent) * -1);   /* except the heading, which backs out */
}

Anything you drop into a section is aligned correctly by default, and there is no way to forget. Never add margin-left to a help page to line something up by hand β€” if it isn't aligned, the markup is in the wrong place.

🚫 No decorative section bands

Every section looks identical: number badge, heading, indented body, a hairline rule to the next one. There is no "highlighted section" variant, deliberately. A variant that means nothing in particular gets applied inconsistently, which is how this whole mess started. If a section needs to stand out, that is a job for its words.


2. Colour

A page declares its accent once β€” and that is all it declares

body {
    --accent:       var(--cmdb-accent);
    --accent-hover: var(--cmdb-accent-hover);
    --accent-soft:  var(--cmdb-accent-soft);
    --on-accent:    var(--cmdb-on-accent);
}

Four lines. assets/css/help.css draws everything β€” the hero gradient, the badges, the note callouts, the card icons, the flow steps β€” from those four variables. Every module already defines its own set in assets/css/theme.css, in both light and dark palettes, so nothing else needs writing. See Theming & Dark Mode for where those tokens come from.

Tickets is the one exception: it has no accent of its own and uses the application's brand colour, which assets/css/help.css already reads from the global --accent. Its style block says exactly that and nothing else.

πŸ”‘ The rule for everything else: colour that carries meaning stays

The sweep had to decide, a dozen times, whether a splash of colour was information or decoration. The rule that settled it:

Colour that carries the meaning stays. Colour that only repeats the label sitting next to it goes.

Kept β€” the colour is the information Removed β€” the label already said it
Service-status dots (operational / degraded / maintenance / outage) Feature-card icons in four unrelated colours down one grid
The morning-checks red/amber/green trend bars and its three RAG cards Flow-chain steps in a per-module rainbow
Change types (standard / normal / emergency) and the change lifecycle badges Knowledge's pending / approved / rejected card borders
The change risk scale, green through to deep red Contracts' four coloured dots beside settings headings
Watchtower's status dots; the LMS progress pills; the mailbox-auth badges

Everything in the "kept" column was moved onto the theme's semantic tokens (--success-*, --warning-*, --danger-*) on the way through, because the originals were hardcoded pastels that only worked in light mode.

⚠️ The dark-mode hero trap

Don't build the hero banner from accent β†’ accent-hover. On dark palettes the module accents are deliberately lifted so they read on dark surfaces, so a full-bleed banner made from them glares. assets/css/help.css handles this already:

.help-hero { background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%); }
[data-theme-mode="dark"] .help-hero {
    background: linear-gradient(135deg, var(--accent-soft) 0%, var(--surface-2) 100%);
    color: var(--text);
}

3. The components

Colour key: 🧱 page skeleton Β· πŸ“ body copy Β· πŸ—‚οΈ grouped content Β· 🏷️ inline Β· πŸ’» code & tables

🎨 Class What it is
🧱 .help-container the flex shell; also where the --help-indent / --help-gutter variables live
🧱 .help-sidebar + h3 the fixed contents rail; h3 is a group heading
🧱 .help-nav-link + .help-nav-num one entry; add .active for the current section
🧱 .help-main the scrolling column (the scroll-spy listens on this, not window)
🧱 .help-hero the banner; takes <h1> or <h2>
🧱 .help-content the padded column the sections sit in
🧱 .help-section + -header + -num level 1 β€” see the heading model above
πŸ“ .help-subsection level 3 β€” a titled block within a section
πŸ“ plain <p>, <ul>, <ol>, <h4>, <h5> styled as direct children of .help-section; no class needed
πŸ“ .help-lede the one sentence that sets up a whole guide
πŸ“ .help-note a callout. Modifiers: .warn, .bad, .ok. Plain = the module accent
πŸ“ .help-back "← Back to …" on a deep-dive page hanging off another guide
πŸ“ .help-muted an aside within a paragraph
πŸ—‚οΈ .help-cards + .help-card the card grid. Modifiers .cols-1 / .cols-3 / .cols-4; 2 is the default
πŸ—‚οΈ .help-card-icon the tinted icon tile. .plain for an emoji (no tile)
πŸ—‚οΈ .help-card.row the one sanctioned card variant: icon left rather than above
πŸ—‚οΈ .help-card-eg the italic "e.g. …" line at the foot of a card
πŸ—‚οΈ .help-steps + .help-step + .help-step-num a numbered procedure
πŸ—‚οΈ .help-defs + .help-def + -term + -desc term-and-definition rows
πŸ—‚οΈ .help-list a plain stack of tinted rows
πŸ—‚οΈ .help-flow + .help-flow-step + .help-flow-arrow A β†’ B β†’ C. Steps are neutral; .accent marks one
πŸ—‚οΈ .help-diagram + -node + -line a monospace tree drawing
πŸ—‚οΈ .help-timeline a worked example told in order
🏷️ .help-pill a status tag. Modifiers .ok .warn .bad .info; plain = neutral
🏷️ .help-dot a small coloured status dot
πŸ’» .help-code, <pre> a code block (same dark scheme in both themes β€” a terminal should look like a terminal)
πŸ’» <code>, .help-kbd inline identifier; a keyboard key
πŸ’» .help-table a wrapper <div> around the <table>, so a wide table scrolls inside its section instead of stretching the page

Note the pill and note modifiers are named for what they mean, not for a colour β€” .help-note.bad, not .help-note.red. That is what lets them follow the theme.


4. Writing a new guide

<link rel="stylesheet" href="../assets/css/theme.css?v=22">
<link rel="stylesheet" href="../assets/css/inbox.css">
<link rel="stylesheet" href="../assets/css/help.css?v=1">
<style>
    /* The only thing a help page should need to say for itself: its colour. */
    body {
        --accent:       var(--mymod-accent);
        --accent-hover: var(--mymod-accent-hover);
        --accent-soft:  var(--mymod-accent-soft);
        --on-accent:    var(--mymod-on-accent);
    }
</style>
<div class="help-container">
    <div class="help-sidebar">
        <h3>Guide</h3>
        <a href="#overview" class="help-nav-link active" data-section="overview">
            <span class="help-nav-num">1</span> Overview
        </a>
    </div>

    <div class="help-main" id="helpMain">
        <div class="help-hero">
            <h2>My module guide</h2>
            <p>One sentence on what the module is for.</p>
        </div>

        <div class="help-content">
            <div class="help-section" id="overview">
                <div class="help-section-header">
                    <span class="help-section-num">1</span>
                    <div>
                        <h3>Overview</h3>
                        <p>The lead paragraph.</p>
                    </div>
                </div>

                <!-- everything from here is level 2 and indents itself -->
                <p>Body copy.</p>
                <div class="help-cards">…</div>
                <div class="help-note">A tip.</div>
            </div>
        </div>
    </div>
</div>

Copy the scroll-spy script from any existing guide β€” it listens on #helpMain and filters on [data-section].

If you find yourself adding layout CSS to a help page, it belongs in assets/css/help.css so every module gets it. What legitimately stays in a page is a diagram of that module and nothing else: the knowledge guide's mock AI conversation, morning checks' trend chart, watchtower's drawing of a real module card, contracts' tab strip, the change lifecycle, the RFP phase strip. Everything else has a 5-to-9-line style block.


5. Traps the sweep found

Worth knowing before you rename anything.

⚠️ Class names live inside translation strings

lang/en/tickets.php contained <span class="tk-help-code">. lang/*/process-mapper.php had pm-help-kbd. lang/*/lms.php had lh-pill β€” across all 21 locales. A grep of the .php pages reports "clean" while the rendered page is broken.

After any class rename in a help page, grep lang/ as well.

⚠️ Don't type a section's number into its heading

Two failures, both from the same cause:

  • self-service numbered two of its nine sections with an empty badge. The numbers were hardcoded in the headings, two sections had been inserted later, and JavaScript patched the numbers at runtime to paper over it. It now derives the number from the $helpNav array in PHP, so the heading and the sidebar cannot disagree β€” and it no longer needs JS to show a number at all.
  • workflow rendered "1." twice, once in the badge and once in the heading text, because the number was baked into the translation string. Stripped from the ten headings in en, pt-BR and uk β€” and note that only 3 of 21 locales had ever carried them, which is its own argument for keeping numbers out of prose.

⚠️ .help-table is a wrapper, not a table class

Two pages had <table class="help-table">. It needs to be <div class="help-table"><table>…</table></div> or a wide table stretches the whole page instead of scrolling inside its section.


6. Verifying a change

Two throwaway scripts earned their keep and are worth rebuilding if you do this again:

  • An orphan-class check β€” extract every class="…" from the markup, and confirm each name has a rule in some stylesheet the page actually links (parse the <link> tags rather than assuming). This is what catches a rename that dropped a rule on the floor: a modifier like .tag.green whose CSS went with the deleted style block, so the badge silently renders plain.
  • A fetch-and-read check β€” pull every help page over HTTP with a forged session and read the body, not the status code. A PHP fatal is served as HTTP 200, so a status-only check reports a broken page as a pass. While you have the HTML, grep it for unresolved *.help.* keys β€” a missing translation falls back to printing the key.

Then a console-error pass in headless Chrome across every page β€” with a negative control, so you know the check can actually fail. Injecting a call to an undefined function into one copy and confirming the check catches it takes ten seconds and is the difference between "no errors" and "no detection".

Two things to know when doing this locally:

  • The portal page needs an ss_user_id session, not analyst_id.
  • system/integrations/help.php redirects without a ?provider= parameter.

Related

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally