Browse all documentation

Customizing CSS

Style the Ridu admin with your own CSS, theme variables, component classes, and optional Sass utilities.

Change the admin’s colors, typography, spacing, and built-in controls with a stylesheet in your application. Ridu exposes CSS variables and named classes, so you can style the existing screens without replacing their Svelte components.

Use custom components when you need different content or behavior, such as a new dashboard panel or field editor.

Add your stylesheet

Create admin/src/custom.css. This example rounds the controls and dashboard cards and makes the login form wider:

admin/src/custom.css
@import '@riducms/ui/layers.css';

@layer app {
  :root {
    --control-radius: 6px;
  }

  .ridu-auth {
    --auth-width: 520px;
  }

  .ridu-dashboard__card {
    border-radius: 10px;
  }
}

Import it from the admin entry:

admin/src/main.ts
import { createAdminClient, mountAdmin } from '@riducms/admin';

import adminConfig from '@/admin.config';
import './custom.css';

const target = document.getElementById('app');
if (target === null) throw new Error('Missing admin mount element.');

mountAdmin({
  target,
  clientFactory: createAdminClient,
  ...adminConfig
});

Run ridu dev and open /admin to see your changes. The entry already loads Ridu’s base theme through @riducms/admin. Your stylesheet is bundled into the admin when you run ridu build.

Override built-in styles

Ridu puts its styles in CSS cascade layers: named groups that determine which styles take precedence. For normal declarations, the order from lowest to highest is:

  1. ridu — the framework’s reset, theme, base styles, components, and utilities.
  2. ridu-plugins — styles added by plugins.
  3. app — your application’s overrides.
  4. Unlayered CSS — ordinary rules outside any @layer.

The layers.css import establishes this order before components load. Use @layer app as in the example above, or write ordinary unlayered CSS. A selector in app can override a more specific framework selector without adding !important. Within the same layer, normal CSS specificity and source order apply. Inline styles and !important declarations follow their own cascade rules.

Keep global overrides in custom.css. For a custom Svelte component, put its own presentation in a <style> block or import a stylesheet beside the component.

Change colors and typography

CSS variables let many components share the same value. Set them on :root to change the whole admin, including menus and dialogs rendered outside the current page.

For example, give primary actions different colors in each theme:

admin/src/custom.css
@import '@riducms/ui/layers.css';

@layer app {
  :root {
    --font-sans: 'Trebuchet MS', sans-serif;
    --control-radius: 6px;

    /* Dark theme: keep text readable on the new action color. */
    --primary: #a5b4fc;
    --primary-foreground: #1e1b4b;
    --accent-hover: #c7d2fe;
  }

  :root[data-theme='light'] {
    --primary: #4338ca;
    --primary-foreground: #ffffff;
    --accent-hover: #3730a3;
  }
}

The admin applies data-theme="light" or data-theme="dark" to the root <html> element, including when the user chooses the system theme. Ridu’s default variables adapt to that choice. Your literal color overrides need their own light and dark values when the same color would be unreadable in one theme. Check both from the account settings.

Colors

--background and --foreground
Page background and main text.
--background-surface and --background-layer
Cards, controls, and raised surfaces.
--foreground-muted
Secondary text.
--primary and --primary-foreground
Primary action background and text. Set both so text stays readable.
--accent-hover
Primary action hover color.
--control-surface and --control-border
Input backgrounds and borders. Use --control-surface-hover and --control-border-hover for hover states.
--field-error-surface and --field-error-foreground
Field error bubble background and text.
--focus-outline and --focus-outline-offset
Keyboard focus outline and its distance from the control.

Fonts and control sizes

--font-sans
Admin text and controls. Defaults to the system sans-serif stack.
--font-mono
Code and structured values. Defaults to the system monospace stack.
--font-richtext-body
Rich-text editor content. Defaults to a serif stack beginning with Georgia.
--font-size-body and --line-height-body
Controls and admin areas that use the body-size variables. Defaults to 13px text with a 20px line height.
--control-height
Inputs and large buttons. Defaults to 40px.
--control-radius
Input and button corners. Defaults to 3px.
--control-padding-inline
Horizontal padding in controls. Defaults to 15px.
--admin-nav-width
Desktop navigation width. Defaults to 275px.
--admin-gutter
Page gutters. Defaults to 60px on desktop, 40px at 1024px, and 16px at 768px.
--admin-sticky-offset
Set by the document editor to the height of its sticky action bar. Use it as top for your own sticky content so it stays below the bar. It is 0px in the live-preview editor pane, which scrolls on its own.

The installed @riducms/ui/src/theme.css contains the complete theme. Feature styles can set more local values: for example, set --auth-width on .ridu-auth, where the login layout defines it. A value set directly on an element takes precedence over a value inherited from an ancestor, even when the ancestor’s rule is in a higher layer.

Style one part of the admin

Inspect the element in your browser and use its named ridu- class. Classes such as .ridu-dashboard__card identify a component part; modifiers such as .ridu-button--primary identify a variant. Use these names instead of generated svelte-… hashes or utility classes.

For example, keep the rest of the theme and add an accent to the active navigation link:

admin/src/custom.css
/* Inside your existing @layer app block. */
.ridu-nav__link--active {
  color: var(--primary);
}

.ridu-nav__link--active::before {
  background: currentColor;
}

Useful starting selectors include:

Login and recovery
.ridu-auth, .ridu-auth__content, .ridu-auth__heading
Navigation
.ridu-nav__link, .ridu-nav__link--active
Dashboard
.ridu-dashboard, .ridu-dashboard__card
Collection list
.ridu-list, .ridu-list-results, .ridu-list-table__scroll
Document editor
.ridu-document, .ridu-document-header, .ridu-document-fields
Inputs and buttons
.ridu-input, .ridu-textarea, .ridu-button
Field labels and feedback
.ridu-field-label, .ridu-field-help, .ridu-field-error-tooltip
Rich text
.ridu-richtext-content, .ridu-richtext-floating-toolbar
SEO
.ridu-seo-overview, .ridu-seo-field, .ridu-seo-preview

Field wrappers also expose data-field-path. Use the path shown in the DOM to narrow an override to an individual field:

admin/src/custom.css
/* Inside your existing @layer app block. */
[data-field-path='title'] .ridu-input {
  font-weight: 600;
}

This matches the title field wherever that path appears. Use a wrapper class from a custom view when the change belongs to only one collection.

Menus and drawers may be mounted outside that field wrapper. Target their own classes for popup presentation; use :root variables when the value should apply throughout the admin.

Use Sass in your own components

Plain CSS is enough for theme overrides. If you prefer Sass, rename the file to custom.scss and change the entry import to import './custom.scss'. Generated projects use @riducms/build, which already includes the Sass compiler.

Import @riducms/ui/styles.scss with @use to reuse control styles and breakpoints. It also declares Ridu’s layer order, so this file needs no separate layers.css import:

admin/src/custom.scss
@use '@riducms/ui/styles.scss' as ui;

@layer app {
  .editor-note {
    @include ui.control-surface;
    padding: 12px var(--control-padding-inline);
    color: var(--foreground);
  }

  @media (max-width: ui.$small) {
    .editor-note {
      padding: 10px;
    }
  }
}

Apply class="editor-note" to an element in your component. The mixin adds its border, radius, background, and shadow. It does not create an input or supply keyboard behavior; use the UI components for interactive controls.

control-surface
Shared resting control border, radius, background, and shadow.
focus-outline($offset: var(--focus-outline-offset))
Outline declarations to include inside your :focus-visible rule. Pass an offset to change its distance from the control.
visually-hidden
Hide content visually while keeping it available to assistive technology.
confirmation-overlay, confirmation-content, confirmation-title
Shared confirmation dialog presentation. Use ConfirmationDialog when you also need its behavior.
$small, $medium, $large
Inclusive max-width breakpoints: 768px, 1024px, and 1440px.

These Sass definitions reuse the runtime CSS variables. To change a color or size, override the variable in CSS; there is no separate Sass theme to configure.

If an override does not appear

  • Check that admin/src/main.ts imports the stylesheet and that the selector matches the rendered element. Creating a file alone does not load it.
  • Put application rules in app or leave them unlayered. Import layers.css before declaring custom layers; the Sass entry does this for you.
  • Check the element’s computed styles. A local variable, inline style, or another application rule may be winning. Put a local variable on the component that defines it.
  • Svelte <style> blocks are scoped to their component. Use the imported global stylesheet to target built-in admin elements.
  • Check light and dark mode and a narrow viewport. If you override --admin-gutter at :root, add your own media queries to retain smaller gutters on mobile.