-
Notifications
You must be signed in to change notification settings - Fork 15
Mobile Friendly
How FreeITSM is being made usable on a phone β the strategy, the one hard rule that keeps it safe, the reusable patterns, and how to bring a new page along. Like Theming & Dark Mode, this is a gradual, nonβbreaking rollout layered on top of the existing desktop app rather than a rewrite or a separate mobile site.
Status: early. Tickets (the inbox) is the first module and the reference implementation. It works and is genuinely usable on a phone, but there is a long tail of polish β squeezing a full ITSM ticket onto a small screen is a lot to ask.
The target is the onβtheβgo analyst β someone away from their desk who needs to triage the inbox, read a ticket, reply, change its properties, and link/record things. It is deliberately not an attempt to make every one of the ~20 modules fully usable on a phone. Some surfaces (the Network Mapper canvas, the Process Mapper, the Gantt timeline, dragβbetweenβcolumns Kanban) are inherently desktop interactions and are out of scope for mobile β they stay desktopβfirst.
So the scope is a focused mobile experience for a handful of highβvalue journeys, done well, starting with Tickets.
This is the north star, and it's what makes the rollout safe to do incrementally:
Every mobile style lives inside a
@media (max-width: 768px)block, and every mobile behaviour is gated onmatchMedia('(max-width: 768px)').
The whole feature is two shared files:
| File | What it is | Safety |
|---|---|---|
assets/css/mobile.css |
Only @media (max-width: 768px) rules β nothing at the top level. |
Above 768px the rules don't exist, so the desktop render is byteβidentical β provable, not by inspection. |
assets/js/mobile.js |
A single IIFE whose every branch checks mq.matches first. |
On a wide screen every handler noβops; injected elements are hidden. |
This mirrors the darkβmode safety net: just as var(--token, #fallback) made tokenising a stylesheet nonβbreaking, the @media/matchMedia gate makes adding mobile behaviour nonβbreaking. You can convert a page and know with certainty you changed nothing for desktop users.
Corollary β injected chrome must be hidden offβmobile. mobile.js injects DOM (a back/folders subβbar, a views hamburger, section sheets). Because mobile.css is @mediaβonly it cannot set a desktop default of display:none. So injected elements are either created with an inline style="display:none" (sheets) or toggled by a syncBar() function that runs on load and on every matchMedia change:
function syncBar() {
var on = mq.matches;
bar.style.display = on ? 'flex' : 'none'; // Back/Folders sub-bar
var vb = document.querySelector('.mobile-views-btn');
if (vb) vb.style.display = on ? '' : 'none'; // views hamburger
if (!on) document.body.classList.remove('mobile-views-open');
}Exactly like theming: perβpage optβin. A page joins the mobile experience by linking mobile.css (and, where it needs behaviour, mobile.js) in its <head>, after its own stylesheet so the @media rules win on ties:
<link rel="stylesheet" href="../assets/css/theme.css?v=18">
<link rel="stylesheet" href="../assets/css/inbox.css?v=37">
<link rel="stylesheet" href="../assets/css/mobile.css?v=6"> <!-- opt in -->
...
<script src="../assets/js/inbox.js?v=52"></script>
<script src="../assets/js/mobile.js?v=4"></script> <!-- after the page's JS -->mobile.js loads after the page's own script so it can wrap the globals that page already exposes (see below) rather than editing them.
The hardest conceptual problem on mobile is navigation: how do you move between modules, between views inside a module, and between panes that normally sit sideβbyβside? FreeITSM answers each with a distinct affordance.
| Layer | Desktop | Mobile |
|---|---|---|
| Between modules | the waffle appβlauncher (topβleft) | the same waffle panel, restyled into a fullβheight left slideβin drawer |
| Between views in a module (e.g. Inbox / Dashboard / Calendar) | the .header-nav button row |
a β° hamburger at the topβright opening a right slideβin drawer |
| Between panes in a view (e.g. folders / list / conversation) | sideβbyβside split panes | an Outlookβstyle masterβdetail stack β one pane on screen, slide between them |
The two drawers are symmetric β modules left, views right β which keeps them easy to tell apart. The waffle drawer needed almost no new code: the appβlauncher panel already existed with open/close JS, so mobile.css just turns the absolute dropdown into a fixed, fullβheight, slideβin drawer.
These are the building blocks. Tickets uses all of them; future modules should reach for the same vocabulary.
A multiβpane split becomes a oneβpaneβatβaβtime slide stack. State lives in a single attribute on <body> (data-mobile-pane), and the panes are absolutely positioned and slid with transform: translateX(...). Wiring it to history.pushState makes the device back button pop the stack, which is what makes it feel native rather than like a resized website.
The canonical .modal-content (used appβwide) fills the screen on mobile β so the reply composer, pickers and forms get room instead of a cramped centred box. One rule, every modal benefits.
When a section crowds a small screen, relocate its DOM node into a fullβscreen sheet opened by a button. Tickets does this for Links, Properties, Time entries and Affected objects. It's configβdriven, so adding another panel is one line.
Action buttons drop their text labels to icons only and move to a bottom bar (via flex order) below the scrolling content β the mobileβapp convention.
Verbose blocks (e.g. a ticket's From/To/Date/Cc) start collapsed behind a tappable summary + chevron, Gmailβapp style.
-
Link
mobile.css(andmobile.jsif it needs behaviour) in the page<head>, after the page's own CSS/JS. Bump the version query when you change them. -
Fix the height model. Desktop shells often use
height: calc(100vh - 48px), which assumes a 48px header β wrong once the header wraps. Switch the body to a flex column on mobile so the header takes its natural height and the content area flexes to fill (flex: 1; min-height: 0). Use100dvh, not100vh, so mobile browser chrome is accounted for. - Collapse sideβbyβside layouts into the masterβdetail stack (or stack them vertically if there are only two).
- Relocate crowded sections into sheets; tighten padding; iconβonly dense button rows; collapse verbose blocks.
-
Wrap, don't edit. If you need behaviour, wrap the page's existing global handlers from
mobile.jsrather than editing the page's large JS file. -
Keep every rule inside the
@mediablock and every behaviour behindmq.matches. Verify:grep -nE "^[^[:space:]/}].*\{" mobile.css | grep -v "@media"should print nothing. - Test on a real device / DevTools device mode. There is no substitute β see the note below.
| Challenge | Solution |
|---|---|
The whole app is a fixedβheight desktop shell (100vh, overflow:hidden, split panes). |
Perβpattern collapse to singleβcolumn / masterβdetail; replace fragile calc() heights with flexbox + 100dvh. |
| Dragβbased surfaces (canvas editors, Gantt, Kanban) don't map to touch. | Out of scope for mobile; they stay desktopβfirst (documented per module). |
| Data tables overflow narrow screens. | Become card lists or horizontalβscroll containers (per module, as they're reached). |
| Can't selfβverify visually β pages need auth and there's no headless browser in the dev box, so screenshots aren't possible. | Build to the spec + prove desktop safety statically (the @media/matchMedia gate), then a tight test loop with a human on a real device. Expect iteration. |
| Injected mobile chrome leaking onto desktop. |
syncBar() mqβtoggle + inline display:none defaults on injected nodes. |
| Module | State | Page |
|---|---|---|
| Tickets (inbox) | In progress β usable, polishing | Mobile: Tickets |
| Everything else | Not started | β |
When another module is brought along, add a Mobile-Friendly-<Module>.md page here and a row above.
- CSS:
assets/css/mobile.cssβ one@media (max-width: 768px)block, organised into numbered LAYERS. - JS:
assets/js/mobile.jsβ onematchMediaβgated IIFE. - Related: Theming & Dark Mode (the same gradual, nonβbreaking philosophy), Architecture.
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- Theming & Dark Mode
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
- MobileβFriendly
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- Problem Management
- Tasks
- Assets
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
- Forms
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)