Limitations and Best Practices

This section covers what custom CSS cannot do, how to diagnose rules that do not take effect, and recommended practices for maintainable, brand-consistent stylesheets.

Limitations

Properties you cannot change

display, position, width, height, z-index, overflow, float, content, and animation are blocked. These affect document flow, stacking context, or layout in ways that could break the portal for all users.

width and height are blocked, but their constrained variants are allowed: max-width, min-width, max-height, and min-height. Use these together with padding and margin for sizing adjustments within a component. The distinction is that max-width and min-width set bounds rather than forcing an exact size, which is safer for a shared layout.

External resources

url() in any value is blocked. You cannot load background images, custom cursors from external URLs, or data URIs via CSS. Image customization (logo, hero, favicon) is handled by the dedicated image upload fields in portal settings.

Fonts

@font-face is blocked. You cannot self-host a font via custom CSS. Use the portal's font selector or the font embed URL configuration for custom typefaces.

Components with CSS variable theming

.sp-accent-divider, .sp-product-tile-accent, .sp-sidenav-item-bg, .sp-search-modal-tab-badge, and .sp-operation-method expose CSS custom properties as their theming hook rather than accepting direct property overrides. The portal injects default values for all of these variables; your CSS overrides them on the relevant selector. See the cascade behavior guidance for the full list.

Side navigation background and hover colors

.sp-sidenav-item-bg background and hover colors are driven by CSS custom properties rather than Tailwind utility classes. Override them on .sp-sidenav (or any ancestor) to retheme all nav item states at once:

.sp-sidenav {
  --sp-sidenav-item-bg: #f8f9fa;
  --sp-sidenav-item-active-bg: #e2f0fb;
  --sp-sidenav-item-hover-bg: #eef4fb;
}
.dark .sp-sidenav {
  --sp-sidenav-item-bg: #1a2535;
  --sp-sidenav-item-active-bg: #1e3048;
  --sp-sidenav-item-hover-bg: #1e3048;
}
Search result count badges

.sp-search-modal-tab-badge background and border are driven by CSS custom properties:

.sp-search-modal-tabs {
  --sp-counter-badge-bg: #d0e8ff;
  --sp-counter-badge-border: #a8cfff;
  --sp-counter-badge-text: #0a2e4d;
}
.dark .sp-search-modal-tabs {
  --sp-counter-badge-bg: #1a3a5c;
  --sp-counter-badge-border: #2a5a8c;
  --sp-counter-badge-text: #d0e8ff;
}
Third-party renderers

The Stoplight Elements and Swagger UI renderers inject their own stylesheets. Custom CSS can reach inside them via descendant selectors (.sp-api-viewer-content .swagger-ui { }), but the level of control is limited by those renderers' own specificity and structure.

Character limit

Custom CSS is limited to 100,000 characters. A full brand stylesheet covering all selectors is typically well under this limit.

Troubleshooting

My rule has no visible effect
  1. Wrong element targeted. Open browser developer tools, inspect the element, and check which class actually carries the style. Several components have an outer .sp-* class and an inner element that carries the explicit color. Targeting the outer class sets an inherited value that the inner explicit class overrides. See the side nav text color guidance for a concrete example.

  2. Property is blocked. If the validator accepted your CSS but the property has no effect, check the blocked properties list. width and height are blocked. Use max-width or min-width instead.

  3. Component uses CSS variable theming. .sp-accent-divider, .sp-product-tile-accent, .sp-sidenav-item-bg, .sp-search-modal-tab-badge, and .sp-operation-method are themed via CSS custom properties. Both a direct background-color or color and the variable override work, but overriding the variable is the recommended approach.

My dark mode rule does not fire

.dark must be the leading token in the selector. It targets an ancestor class on html, not the component element itself. Make sure you are writing .dark .sp-topbar { }, not .sp-topbar.dark { }.

My CSS custom property is not resolving

Custom properties cascade through the Document Object Model (DOM) from parent to child. If you define --brand-primary on .sp-topbar, it is only available inside .sp-topbar descendants. To use a variable across unrelated components, define it on a common ancestor. For example, set it on .sp-topbar and also on .sp-hero separately, or use :root (which is always allowed as a non-.sp- selector when accompanied by a .sp-* rule in the same stylesheet).

The validator rejected my CSS but I cannot see why

Common causes that are easy to miss:

  • A url() anywhere in a value, including background: url(...), cursor: url(...), or a data URI.

  • An unknown .sp-unknown-name class. It must be exactly from the selector API table.

  • @font-face or @import at the top of the file.

  • visibility: hidden. Only visible is allowed.

Best practices

Organize by section

Group rules by component area using comment blocks. The portal has roughly 15 logical sections. Keeping rules grouped makes it easier to maintain:

/* TOP BAR */
.sp-topbar { ... }
.sp-topbar-logo { ... }

/* HERO */
.sp-hero { ... }
.sp-hero-description { ... }
Write light and dark together

Place each selector's dark variant immediately after its light variant. This is easier to maintain than putting all dark rules at the end:

.sp-sidenav { background-color: #ffffff; border-right: 1px solid #e8eded; }
.dark .sp-sidenav { background-color: #0e1e18; border-right-color: rgba(156, 193, 180, 0.15); }
Use CSS custom properties for design tokens

Define your palette once as custom properties on a root selector, then reference them everywhere. This makes global color changes a one-line edit:

.sp-topbar {
  --brand-primary: #004831;
  --brand-accent: #c4d700;
  --brand-text: #ffffff;
  background-color: var(--brand-primary);
  color: var(--brand-text);
}
.sp-accent-divider { --sp-accent-divider-color: var(--brand-accent); }
.sp-product-tile-accent { --sp-product-tile-accent-color: var(--brand-accent); }

Custom properties cascade normally, so defining them on a parent selector makes them available to all descendants.

Avoid the !important flag

The scoping mechanism gives your rules enough specificity to win most cascade battles without !important. No !important is needed anywhere in custom CSS.

Test both dark and light modes before saving

The Preview button opens the portal with your current unsaved CSS applied. Check both modes, and check on mobile screen widths if you are using @media rules.

Use the reference panel

The Custom CSS editor includes a collapsible selector reference listing all available .sp-* classes with their component descriptions. This is the authoritative list; the validator rejects any class not in it.

Prefer descendant selectors for inner elements

Several components have inner elements that must be targeted separately from the outer .sp-* class, such as the side nav item text and the profile menu rows. When a rule has no visible effect, check whether the property is applied to the right element in the component hierarchy using browser developer tools.

Publication date: