Previous | Index

CSS: Defaults, Overrides, and Structure

This guide explains how to customize SimplyPages styling safely without forking framework CSS.

Where Default CSS Comes From

The framework ships a default stylesheet at:

  • simplypages/src/main/resources/static/css/framework.css

It is included by ShellBuilder in generated HTML as:

  • <link rel="stylesheet" href="/css/framework.css">

ShellBuilder CSS Controls

Use ShellBuilder to control the stylesheet chain:

  • withFrameworkCss(boolean) toggles framework CSS on/off.
  • withFrameworkCssPath(String) replaces the framework CSS href.
  • withCustomCss(String) sets one custom stylesheet.
  • withCustomCss(List<String>) sets an ordered list of custom stylesheets.
  • addCustomCss(String) appends one custom stylesheet.

CSS Load Order

ShellBuilder.build() loads styles in this order:

  1. framework CSS (default /css/framework.css, or withFrameworkCssPath(...))
  2. custom stylesheets in configured order

If withFrameworkCss(false) is set, only custom stylesheets are included.

Shared Framework Tokens

framework.css exposes shared :root variables with --sp-* names.

Token model:

  • Preferred semantic tokens: --sp-theme-* (surfaces, text, borders, accents, status colors, gradients).
  • Component tokens: --sp-banner-*, --sp-top-nav-*, --sp-home-*, --sp-hero-* for focused shell/module retheming.
  • Compatibility tokens: --sp-color-* (retained so older overrides still work).

Start customization by overriding semantic tokens in your app CSS:

:root {
  --sp-theme-accent: #0f766e;
  --sp-theme-accent-hover: #115e59;
  --sp-theme-surface-raised: #f8fafc;
  --sp-gradient-surface-panel: linear-gradient(180deg, #ffffff, #f3f7ff);
  --sp-space-md: 24px;
  --sp-radius-lg: 12px;
}

If you already use --sp-color-* overrides, they remain valid. New projects should prefer --sp-theme-* for clearer intent and broader coverage.

Where You Put Your App CSS

For Spring Boot applications, place app CSS in:

  • src/main/resources/static/css/app.css (global app CSS)
  • src/main/resources/static/css/pages/<page-name>.css (page-specific CSS)
  • src/main/resources/static/css/modules/<module-name>.css (module-specific CSS)

Reference one or many files through ShellBuilder:

String html = ShellBuilder.create()
    .withPageTitle("Portal")
    .withCustomCss("/css/app.css")
    .addCustomCss("/css/pages/analytics.css")
    .addCustomCss("/css/modules/sales-summary.css")
    .build();

Cross-App CSS vs Page CSS vs Module CSS

Recommended structure:

  • Cross-app/global rules in /css/app.css
  • Page-level rules in /css/pages/...
  • Module-level rules in /css/modules/...

Load order strategy:

  1. framework CSS
  2. app global CSS
  3. page CSS (if needed)
  4. module CSS (if needed)

Later files win on equal specificity.

How to Target Pages and Modules Safely

Use stable IDs/classes from your components and modules.

Examples:

Page page = Page.builder()
    .addComponents(new Div().withId("analytics-page"))
    .build();

ContentModule module = ContentModule.create()
    .withModuleId("sales-summary")
    .withClass("sales-summary-module");

Then in CSS:

/* page-level */
#analytics-page .module {
  margin-bottom: 24px;
}

/* module-level */
#sales-summary .module-title,
.sales-summary-module .module-title {
  letter-spacing: 0.02em;
}

Override and Precedence Rules

CSS precedence in practical terms:

  1. inline style attribute (highest)
  2. stylesheet rules by specificity
  3. stylesheet rules by source order (later wins if specificity ties)

In SimplyPages code:

  • .withClass(...) / .addClass(...) adds classes for stylesheet-driven control.
  • .addStyle(...) and size helpers (withWidth, withMaxWidth, withMinWidth) write inline style.

Use inline style sparingly for runtime-calculated values.

Button Visibility and Contrast

The framework's .btn rule hides buttons with ordinary hidden using display: none, so they are not painted or included in sequential keyboard navigation under the default styles. It deliberately excludes hidden="until-found" (case-insensitive), leaving that state to browser behavior rather than forcing display: none. Caller CSS or inline styles can override the default display rule; check visibility and keyboard behavior when customizing it. Hiding a control is not authorization.

The default secondary button uses white text over the --sp-button-secondary-bg gradient, with a separate --sp-button-secondary-bg-hover gradient. The default Add Module secondary control's normal text is intended to meet 4.5:1 contrast across the gradient. If you override these tokens, text color, or button styles, check contrast across the resulting gradients and states and preserve visible focus. This targeted default is not a certification of accessibility for the whole framework or an application.

Should You Edit framework.css Directly?

Default answer: no.

  • Preferred: override --sp-* tokens and layer app selectors in your own CSS files.
  • Edit framework framework.css directly only when contributing upstream framework defaults.
  • If you fork framework CSS in an app, expect manual merge work on framework upgrades.

Minimal Practical Setup

  1. Keep framework defaults enabled.
  2. Add /css/app.css using withCustomCss(...) or withCustomCss(List<String>).
  3. Namespace page styles under a page root ID.
  4. Namespace module styles under module IDs/classes.
  5. Keep overrides in CSS, not scattered inline styles.

Mobile Defaults in Framework CSS

framework.css includes responsive defaults for shell and layout primitives:

  • mobile sidebar off-canvas behavior (.main-sidebar.mobile-open)
  • mobile sidebar toggle styling (.mobile-sidebar-toggle)
  • row/column stacking for narrow screens
  • text wrapping guards on columns/module content
  • sticky sidebar mobile stacking/collapse hooks

Treat these as baseline behavior and layer app-specific responsive polish in custom CSS.

Advanced Mode: No Framework CSS

You can disable framework CSS:

String html = ShellBuilder.create()
    .withFrameworkCss(false)
    .withCustomCss("/css/app.css")
    .build();

This is supported for advanced integrations. Without replacement styles, many built-in components may appear visually broken because they rely on framework class styling.

Next Reading

For internals and behind-the-scenes load behavior, read:

  • docs/core/06-shell-project-structure-and-asset-load-chain.md