Skip to content

DESIGN.Crm

CRM is the internal application used by WarmlyYours staff to run sales, fulfillment, customer support, accounting, and operations. The audience is employees, not customers, and they live in the app for hours a day. The visual identity therefore prioritizes:

  1. Density. Get more on screen. Compact tables, tight padding, scannable typography.
  2. Speed. No custom font loading, no decorative imagery, no animation beyond functional state changes.
  3. Recognition. Same brand burgundy as WWW, but used utilitarianly — for the navbar, primary buttons, and brand moments only. Most surfaces are neutral.

The system is Bootstrap 5.3 with a thin theming layer (client/stylesheets/crm/bs.scss) that swaps colors but keeps Bootstrap’s typographic and spatial defaults intact. RailsbootUI ViewComponents sit on top for higher-level patterns (page headers, action menus, breadcrumbs, badge stacks).

CRM is a Hotwire app — Turbo Frames for partial updates, Stimulus for interactivity. Every component should degrade to a working server-rendered form. Avoid client-only interactions that strand a Turbo navigation.

Three tiers, in priority order:

Identity. primary (#913120) is the same burgundy spirit as WWW but shifted slightly for legibility against dense data. Use for primary buttons, the navbar background, focused brand moments. Do not use as a fill on data surfaces — burgundy on a data table is visual noise.

Neutrals. white is the canonical surface for data. light-shade (#FBF9F8) is the warm app background and Bootstrap’s $light, so it also backs panels and .bg-light utilities — it differentiates chrome from content. gray-100 is the table-zebra/header neutral. Borders are a single #DDDDDD (border token); do not invent new border greys.

The gray-100gray-900 scale is piped into Bootstrap’s $grays map in crm/bs.scss, which is why several entries carry no component of their own — they are consumed by Bootstrap utilities, not by a named component. An unreferenced grey is not a dead token here.

Domain accents. The phone-* palette has business meaning. There are exactly six presence states, and the palette mirrors PhonesHelper::PRESENCE_DOT_CLASSES one-for-one — available, available-home-office, available-non-queue, dnd, away, unknown. They are not theming colors and must not be repurposed. Adding a seventh state requires a coordinated UX + business decision, and means touching the helper, partials/_navbar.scss, and this file together.

The dots are non-text indicators: the state name always renders as text beside the dot, never inside it, so the color never has to carry a 4.5:1 text ratio. Switchboard buttons derive their pair from the same token (background: lighten($color, 40%), color: darken($color, 10%) — the phone-status-change-box mixin), so the token stays the single source.

Links inside CRM are info (teal-green #307E6B), not brand burgundy. This deliberately separates “navigate to” from “perform action.” Burgundy is reserved for action.

System UI stack only — no @font-face loading. CRM ships zero font bytes, which keeps perceived load times fast and lets the OS render at native hinting. The stack starts with -apple-system / BlinkMacSystemFont so macOS and Windows both render in their native UI font, falling back through Segoe UI, Roboto, etc.

Default body weight is 400. 500 is the heading weight; 600 is reserved for body-emphasis, table headers, and small caps moments. There is no bold weight in normal use — font-weight: bold should be considered a smell on this surface.

monospace exists for IDs, SKUs, account numbers, code blocks, and any value that benefits from fixed-width alignment. Reach for it generously in tables of identifiers — that’s a design feature, not a default.

The H6 token has slight letter-spacing (0.02em); it’s used as a small-caps section label inside cards and panels.

Bootstrap 5.3 grid, default breakpoints (no xxl extension — the WWW site adds one, CRM does not). Container max-widths are Bootstrap defaults. Most CRM pages use container-fluid so data tables breathe at large widths.

Spacing scale follows Bootstrap 0–5. Reach for 0, 1, 2, 3 for intra-component spacing; 4 and 5 for between cards and major sections. Do not introduce a 6+ extension — CRM density does not benefit from larger vertical rhythm.

Page header pattern: H1 + breadcrumb + actions row, all on white. The breadcrumb sits on a barely-tinted background (rgba(0,0,0,0.03)) so it visually precedes the H1 without claiming attention. Use the crm-pages skill conventions for the canonical structure.

Flat. CRM has no sanctioned shadow elevation outside of Bootstrap’s modal/ dropdown defaults. Cards differentiate via 1px borders (border token), not shadows. Sticky headers in long pages get a 1px bottom border on scroll — nothing more.

If a surface needs to feel “lifted,” check whether it actually needs to — density usually wants the opposite.

Bootstrap 5.3 defaults — 0.375rem (rounded.md) on buttons, inputs, cards. Pills (rounded.pill) for badges and status chips only. Tables, navbars, and sidebars are square-cornered. Avatars are circles (rounded.pill applied to square images).

Buttons. Primary is brand burgundy; reserve for the dominant action on the page (one per significant region). Secondary (slate #5A7B81) for “alternative.” Success (green) for create/confirm; danger (red) for delete/cancel. Outline variants for tertiary actions and toolbars.

Forms. Bootstrap default treatment — white background, gray border, default focus ring. No brand red overlay on inputs. Form labels use body-emphasis.

Dense floating-label forms. Bootstrap’s floating-label control is 3.5rem tall, of which 0.625rem is dead space under the value. Forms that put eight or more fields on one card — the activity form is the reference implementation (client/stylesheets/crm/partials/_activities.scss) — compact it to 3.125rem by trimming that padding to 0.25rem, keeping Bootstrap’s 1.625rem label-above padding untouched. Derive the height from one variable per form ($activity-field-height) so the input-group add-ons that sit beside those fields stay the same height. Don’t compact a form below 3.125rem, and don’t apply this to ordinary forms — the stock height is the default everywhere else.

Compound controls (a date and its time, an address and its country) belong in one .input-group with icon add-ons rather than separate fields with gaps. Give the narrower field a fixed flex-basis sized to its longest value so it can’t be the one that clips, and let the wider one take the remainder.

Tables. White surface, gray-100 headers, border token on row dividers. Compact padding (0.5rem cell padding) is the default. Row hover uses gray-100. Use the tables partial conventions in client/stylesheets/crm/partials/_tables.scss rather than hand-styling.

Page header. H2 (not H1 — CRM convention reserves H1 for the document title set by Rails) on white, breadcrumb above, actions to the right. The crm-pages skill defines the exact structure for ViewComponent assembly.

Navbar. navbar-bg (#7F2024 — a deeper burgundy than primary) for contrast against page content. White text. Active link state is brand primary (#913120).

Badges. Pill-shaped, caption-sized type, semantic background. Presence states use the phone-dot-* tokens and must be applied via PhonesHelper#icon_for_presence and the presence-dot-* classes, not arbitrary CSS.

RailsbootUI components are the canonical assembly layer. Reach for Railsboot::PageHeaderComponent, Railsboot::CardComponent, etc., before authoring new ViewComponents — see the view-components and ui-conventions skills.

Every interactive element expresses five states. CRM themes only hover; the rest are Bootstrap 5.3 defaults on purpose.

  • Hoverprimary-hover (#B53D28) on primary buttons, dark-shade on links, gray-100 on table rows. Hover is a lightness shift, never a new hue.
  • Focus — Bootstrap’s default focus ring, unmodified. Never remove it; CRM is a keyboard-heavy surface and staff tab through forms all day. Use :focus-visible when a mouse-click ring reads as noise (see partials/_image_library.scss).
  • Active/pressed — Bootstrap default. Active navbar links use primary.
  • Disabled — Bootstrap default (opacity: .65). Do not author a custom disabled palette, and never convey “disabled” with color alone — a disabled control in a dense table must still read as disabled in grayscale.
  • Loading — Turbo owns it (shared/_turbo.scss). Don’t hand-roll button spinners; a Turbo Frame’s own busy state is the pattern.

Empty states get a one-line explanation plus the action that fills them — never a bare blank table body.

Effectively none. CRM animates only what communicates state: Bootstrap’s .15s ease-in-out transitions, dropdown/offcanvas/modal reveals, and Turbo Frame swaps. No scroll-driven effects, no entrance animations, no skeleton shimmer. Staff open the same screen a hundred times a day and every animation is a hundred small delays.

Any animation added anyway must respect @media (prefers-reduced-motion: reduce).

Font Awesome 7.2 Pro, Sharp onlysharp-solid, sharp-regular, and brands are the bundles imported by crm/crm.index.scss (vendored under client/vendor/fontawesome/). Classic, Light, Duotone, and Thin are not bundled and render as blank boxes.

Emit icons through IconHelper#fa_icon (<%= fa_icon('gear') %>), never a raw <i class="fa-…"> — the Heatwave/NoInlineFaIcon RuboCop cop enforces it. An icon-only button (toolbars, table row actions) always carries an aria-label or a tooltip title.

Bootstrap 5.3 defaults, no custom tier — sm 576, md 768, lg 992, xl 1200px. Most pages are container-fluid so tables use the full width.

CRM’s primary target is a desktop monitor, but the surface must stay usable on a tablet in a warehouse or on a phone at a job site:

  • Wide tables wrap in .table-responsive (172 CRM views already do) and scroll horizontally. Never drop columns at a breakpoint — staff read this data to make decisions, and a hidden column is a wrong decision.
  • Secondary panels (job tracker, pinned searches, advanced search, communications) are offcanvas drawers, not squeezed sidebars.
  • The 16rem sidebar collapses below lg; the page header, breadcrumb, and actions row stack rather than truncate.
  • Touch targets are 44 × 44 px minimum on pointer-coarse devices. Dense desktop padding is fine on a mouse; row-action icons are not.

There is no dark mode. Bootstrap 5.3 ships color modes and we do not use them; nothing in the codebase sets data-bs-theme or reads prefers-color-scheme.

Do start every CRM page from the crm-pages skill template. The page header / breadcrumb / actions pattern is non-negotiable for navigation consistency.

Do use system UI fonts. CRM ships no font files.

Do use info (teal) for links and primary (burgundy) for action buttons. The split keeps “click to navigate” visually distinct from “click to do.”

Do lean on Bootstrap utilities and RailsbootUI components before writing new SCSS or ViewComponents.

Do use monospace for SKUs, IDs, account numbers, and any data where column alignment matters.

Do emit every icon through fa_icon, in the Sharp family, and give icon-only buttons an aria-label.

Do wrap wide tables in .table-responsive and let them scroll.

Don’t load custom fonts on CRM. The system stack is the standard.

Don’t use the phone-* palette for theming, and don’t invent a presence state that isn’t in PRESENCE_DOT_CLASSES. Those colors carry business meaning; misuse confuses switchboard operators.

Don’t introduce shadow elevation. Use 1px borders to separate surfaces.

Don’t extend the spacing scale beyond Bootstrap’s 5. CRM is dense by design.

Don’t use brand burgundy as a content-area background. It’s an identity color, not a fill — keep it in chrome (navbar, primary buttons, brand spots).

Don’t use H1 inside the page body — Rails sets <title>, and the page header is H2 by convention.

Don’t hide table columns at a breakpoint. Scroll horizontally instead — a missing column is a wrong decision.

Don’t convey state with color alone. Phone status, row status, and validation all need a label, icon, or text alongside the color.

Don’t add entrance animations, scroll effects, or skeleton shimmer. Staff open these screens hundreds of times a day.

Don’t add dark mode or data-bs-theme. The surface is light-only.

Don’t remove focus rings. CRM is keyboard-driven.