Back to notes

UX/UI Design

Building SaaS

A working reference for designing multi user, multi tenant software: sign in and security, tenancy, roles and seats, the app shell, tables and bulk actions, collaboration, billing and its failures, admin and audit, performance, accessibility and the metrics that show it is working. The decisions that recur in every SaaS product and are expensive to unwind later.

Muhammad Saud Musaddiq15 min read

Anatomy of SaaS

Every SaaS product has five surfaces whether you design them or not: the marketing site, the signed out states (sign in, invite, reset, expired), the app, the admin and billing area, and the internal tooling your own support team uses. Teams design the app and inherit the other four. Scope all five on day one, because the ones you skip are the ones users meet when something has gone wrong.

CoversFive surfacesInternal toolingSigned out statesScope early
Five stacked rows, one per surface: the marketing site, signed out states, the app, admin and billing, and internal tooling, each with its URL or contents and tagged Inherited apart from the app, which reads Designed.

Tenancy and Workspaces

Decide the tenancy model before the first screen: one user with personal data, one workspace per company, or one user in many workspaces. The choice sets the URL structure, the switcher, what a share link can reach and whether a personal account can convert to a team later. Cross tenant reads are a bug by default; every screen shows exactly one workspace and says which.

CoversTenancy modelURL structureCross tenant readsWorkspace conversion
Three model panels above a switcher: personal, workspace and multi workspace with the URL each produces, Acme at 12 members beside Globex at 3, and three rules covering workspace isolation and personal to team conversion.

Accounts and Sign In

Sign in starts with one field, the email, and routes from there: password, magic link, Google, or the company SSO if the domain is claimed. Domain capture means anyone with @acme.com lands in the Acme workspace, with an admin approving if the workspace is closed. Session length is a workspace setting and the user can see and end every active session.

CoversOne fieldDomain captureMagic linkSession policy
One email field with four branches: password, magic link, Google or Acme SSO, each tagged with the condition that picks it, above three active sessions and an End all others control.

Security and Enterprise Readiness

Enterprise buyers ask for the same six things in every questionnaire: MFA with passkeys, SAML or OIDC SSO per workspace, SCIM provisioning and deprovisioning, an audit log, role based access with least privilege defaults and data residency. Build in the order customers ask: roles, audit log, SSO, then SCIM. Removing a user through SCIM must end their sessions and API keys immediately, not soft delete a record that still authenticates.

CoversMFA and passkeysSAML and OIDCSCIMAudit logOrder of build
Six numbered cards in build order: roles, audit log, SSO, SCIM, MFA and passkeys, then data residency, with a panel tracing a SCIM deactivation through ended sessions, revoked API keys and sign in blocked immediately.

Roles, Members and Seats

Roles are a matrix, objects down and roles across, published where admins can read it. Members move through invited, active, suspended and removed, and each state has a visible reason. Seats are counted at the moment that matters for billing, invite or activation, and the invite screen says which. Offboarding transfers ownership of everything the person owned before the account closes.

CoversPermission matrixMember statesSeat timingOffboarding
A four by four permission grid: objects down and roles across in ticks and dashes, with member states from invited to archived, a seat counted on activation not invite, and a removal transferring 4 projects and 2 API keys.

Information Architecture and Navigation

Navigation follows the object model: nouns the user owns in the sidebar, verbs in the toolbar, settings at the bottom. Two levels is the limit; a third level means the object model is wrong. Tree test the labels before building. Add a command palette early, ⌘K, because power users will navigate by typing within a month and it doubles as search.

CoversObject modelTree testingCommand paletteNav depth
An app mock with two label sets: sidebar nouns against toolbar verbs, a command palette open on a partial query returning Go to, Open and Create, and a note that a third level means the model is wrong.

The App Shell

The shell is what every route inherits: a sidebar of 240 collapsing to 64, a 56 topbar with breadcrumb, search and account, and a content area that owns its own scroll. The shell never scrolls. Every route has a URL that can be shared and reopened to the same state. Editors and canvases get a full screen mode that hides the shell without losing the route.

CoversShell geometryScroll ownershipRoute parityFull screen mode
A shell mock dimensioned in place: a 240 px sidebar collapsing to 64, a 56 px topbar, a breadcrumb tied to a route URL carrying view and sort, and a full screen mode that keeps the route.

Components, States and Formats

Every component in a SaaS app owes six states: loading, empty, partial, error, read only and full. There are four kinds of empty: first use, filtered to nothing, permission denied and deleted, and they need different copy. Formats are a workspace setting: date, number, currency, time zone, and every number on every screen honours it.

CoversState coverageFour emptiesRead onlyFormatsLocalisation
Six state mocks over four empty states: loading, empty, partial, error, read only and full, then first use, filtered, no permission and deleted, each carrying the copy that belongs to it.

Onboarding, Import and Export

Onboarding is a race to the first real result with the user's own data, so import comes before the tour. Import maps columns visibly, previews the first rows, and finishes with partial success: 240 rows in, 3 skipped, here is the file of the three. Export is the same shape in reverse and covers everything, because a product that traps data is a product people leave angrily.

CoversTime to valueColumn mappingPartial successError reportFull export
A five step rail with two panels: upload through report, a column mapping where Phone No maps to Skip, and a finished import reading 240 imported and 3 skipped with those three offered as a file.

The Object Lifecycle

Decide the save model per object type: autosave for documents, explicit save for settings and anything with side effects. Guard dirty state on navigation. Archive hides and preserves; delete removes with a retention window, 30 days, during which restore is one click. Undo covers every reversible action for at least ten seconds and says what it will undo.

CoversSave modelDirty guardArchive vs deleteRetention windowUndo
A five stage rail with three save models: draft to purged with the 30 day restore window marked, autosave, dirty guard and confirm split by object type, and an archive toast offering undo for 10 seconds.

Tables, Views and Bulk Actions

The working table is the heart of most SaaS apps. Rows 48 or 36, first column frozen, sort and filters in the URL so a view can be saved and shared. Select all offers this page or all 4,212 matching, and says which. Bulk actions report partial failure per row, never a single error for a batch that half succeeded.

CoversRow heightFrozen columnURL stateSelect allPartial failure
A table mock with its selection bar live: filter chips echoed in the URL, a frozen first column, an option to select all 4,212 matching, and a bulk action reporting 1 of 2 reminders failed and why.

Collaboration and Sharing

Pick a concurrency model per object: real time merge for documents, last write wins with a staleness warning for records, soft locking for anything financial. Show presence where two people can collide. Share links have a scope, workspace, anyone with the link, specific people, and the scope is visible on the link itself. Guests get a role, not an exception.

CoversConcurrency modelPresenceStalenessLink scopeGuests
Three concurrency models beside a share panel: a document merged in real time with presence avatars, a client record warning that Leo changed it two minutes ago, an invoice locked by Amira while she edits, and a share link carrying its own scope.

Assistive Features

An assistant inside SaaS inherits the permissions of the user who asked and nothing more. It suggests by default and acts only with a confirm, and everything it writes is attributed to it in the activity log. Sources are shown inline. Every workspace has an off switch for assistive features, and it works.

CoversPermission scopeSuggest vs actAttributionShown sourcesOff switch
Five bounds beside an activity log: inherited permissions, suggest before act, attribution, shown sources and a working off switch, with Assistant entries in the log sitting next to the human who accepted them.

Feedback, Notifications and Email

Feedback for the current user is a toast; anything for someone else goes to the inbox and, if they are away, to email. Email is digested by default, muted per channel, and every message deep links to the exact object. The emails you actually send are few: invite, reset, receipt, digest, expiring, failed payment. Design each one, including the expired invite.

CoversToast vs inboxDigestPer channel muteDeep linksExpired invite
A three way routing rule with samples: toast for me now, inbox for someone else, digest when they are away, plus the six emails actually sent, the expired invite, and three per channel mutes.

Settings and Connected Accounts

File settings by owner, not by feature: my settings, workspace settings, billing. Each setting says its scope in the label. Connected accounts show health: connected, expiring, broken, and who connected them, because the token dies when that person leaves. Disconnect explains what stops working before it does.

CoversSetting ownershipScope labelsHealth statesToken ownerDisconnect
Three settings groups beside three integrations: my settings, workspace and billing filed by owner not by feature, then Slack healthy, Google Drive expiring in 3 days, and Salesforce failed because the person who connected it left.

API, Webhooks and Integrations

The API is a surface with its own UI: keys created with a scope and a name, shown once, listed with last used. Webhooks are configured with an endpoint, event picker and a delivery log with retry. Rate limits are visible before they bite. Integrations directory lists what connects, what it needs and who installed it.

CoversAPI keysScopesWebhooksRetry and logsRate limits
An API surface in four blocks: a keys table with scope and last used, a secret shown once, a webhook subscribed to invoice.paid and invoice.failed, and a delivery log pairing a 200 with a 500 on retry 2 of 5.

Plans, Trials and Upgrades

The plan page is a matrix of three plans with the differences visible, not a feature list per plan. The trial has a visible clock and converts without losing anything. Upgrade prompts appear at the limit, with the number, and never as a modal on sign in. Downgrade is self serve and says what will be lost, and enterprise means talk to us, not a hidden fourth column.

CoversPlan matrixTrial clockUpgrade promptsDowngrade pathSelf serve
Three plan columns with two panels: Starter at $0, Team at $12 a seat marked current, Enterprise as talk to us, a trial counting 9 days left, and an upgrade prompt reading 100 of 100 invoices used.

Billing, Limits and Lapse

Usage meters live in settings and warn at 80 percent. A failed payment starts a grace period, seven to fourteen days, with email and in app banners, then read only mode, never deletion. Read only keeps every screen visible and every export working. Seat downgrade asks who leaves, and invoices and receipts are always downloadable, even after churn.

CoversUsage metersGrace periodsRead only modeSeat downgradeFailed payment
A four point timeline with three panels: day 0 failure to day 45 export and never deletion, a banner naming the date the card must be updated by, a read only screen where export still works, and usage meters warning at 80 percent.

Admin, Audit and Trust

Admins get an audit log with six parts per row: who, did what, to which object, when, from where, and the before and after. Support impersonation is logged, time boxed and visible to the user. Active sessions can be ended. Retention and deletion policies are stated in the product, and a trust page lists certifications, subprocessors and incident history.

CoversAudit logImpersonationActive sessionsData retentionTrust page
Three audit rows split into six numbered columns: who, did what, object, when, from and the before and after change, including a support impersonation and a SCIM deprovision that cut sessions, above a banner ending support access in 18 minutes.

Errors, Recovery and Support

Errors fall into three classes: the user can fix it, the system will fix it, and nobody knows yet. Each class has its own message shape. Every unexpected error carries an ID the user can quote to support. Support is reachable from inside the app with the context attached. A changelog and a status page are product surfaces, not marketing.

CoversError classesInline messagesError IDsChangelogStatus page
Three failure classes with their message shapes: an inline field error, a self clearing banner, and an error ID, above a support form with workspace and route attached and a status page beside a changelog.

Performance Budgets

Design to bands: under 100 ms feels instant, under one second needs no feedback, under ten needs a skeleton or progress, over ten is a background job. Progress is real when possible, 340 of 1,200 rows, not a spinner. Set a payload budget per route and test with the largest tenant data, because the demo workspace lies.

CoversResponse bandsSkeletonsReal progressPayload budgetLargest tenant
Four latency bands with treatments: under 100 ms to over 10 seconds, a real progress readout of 340 of 1,200 rows, and one route budget of 180 KB JS tested against a 48k row tenant.

Density, Responsiveness and Accessibility

SaaS is desktop first with density presets the user chooses. Mobile is scoped: reading, approving, notifying, not the whole app. The keyboard floor is that every action reachable by mouse is reachable by keyboard, with visible focus. Targets are 24 px on desktop and 44 on touch, and the whole app runs at 200 percent zoom without horizontal scroll.

CoversDesktop firstDensity presetsMobile scopeKeyboard floorTarget sizes
Two density presets over one table: comfortable against compact, a 36 row named as a user setting, four access numbers at 24 px desktop, 44 px touch, 200 percent zoom and 100 percent keyboard reach, and mobile scoped to read, approve and notify.

Dashboards and Metrics

Dashboards default to a sensible range, last 30 days, with the range visible and the comparison stated. Zero is a value and shows as zero; empty means no data and says why. Every metric has a definition on hover. For the product itself, instrument activation, time to value, weekly active workspaces and seat expansion, with an event schema agreed before launch.

CoversDate rangesZero vs emptyMetric definitionsActivationEvent schema
Four metric cards above two rules: activation, time to value, weekly active workspaces and seat expansion, each with a value, a change and a definition, then show a real zero, and say why when there is no data.