Components

Learn how and why we build nearly all our components responsively and with base and modifier classes.

Base classes

CoreUI’s components are largely built with a base-modifier nomenclature. We group as many shared properties as possible into a base class, like .btn, and then group individual styles for each variant into modifier classes, like .btn-primary or .btn-success.

To build our modifier classes, we use Sass’s @each loops to iterate over a Sass map. This is especially helpful for generating variants of a component by our $theme-colors and creating responsive variants for each breakpoint. As you customize these Sass maps and recompile, you’ll automatically see your changes reflected in these loops.

Check out our Sass maps and loops docs for how to customize these loops and extend CoreUI’s base-modifier approach to your own code.

Modifiers

Deprecated in v6.0.0

Components used to be built with a base-modifier class approach: the bulk of the styling in a base class (e.g., .btn) and each color in its own modifier (e.g., .btn-danger), generated by looping over $theme-colors. Those classes still ship, in the opt-in coreui-legacy.css rather than in coreui.css — but a component is themed with a theme variant now, so one family of classes recolors every component instead of one family per component.

Here are two examples of the loops that still generate them, for .alert and .list-group.

scss
@each $state in map.keys($theme-colors) {
  .alert-#{$state} {
    @include theme-tokens($state);
  }
}
scss
// List group contextual variants
//
// Add modifier classes to change text and background color on individual items.
@each $state in map.keys($theme-colors) {
  .list-group-item-#{$state} {
    @include theme-tokens($state);
  }
}

Theme variants

Themeable components also support a single family of composable .theme-* classes that recolor a component without a per-component color class. A themeable component reads a shared set of generic --cui-theme-* tokens for its background, foreground, border, and focus styles — each with a fallback to the component’s own default token. When no theme class is present, the fallback applies. Add a .theme-* class to the component, to an ancestor, or to one of its parts, and it picks up that theme’s colors instead.

TokenDescription
--cui-theme-bgThe theme color itself, e.g. a solid badge or button background
--cui-theme-contrastContrasting color for text and icons sitting on the solid base
--cui-theme-fgThe theme’s readable text color
--cui-theme-fg-emphasisHigh-contrast foreground, one step darker
--cui-theme-bg-subtleSubtle background tint for low-emphasis surfaces
--cui-theme-bg-mutedMuted background, between subtle and solid
--cui-theme-borderBorder color
--cui-theme-hoverThe theme color shifted for a hover state: lightness times --cui-theme-hover-lightness, chroma times --cui-theme-hover-chroma
--cui-theme-activeThe same, one step further, for a pressed state, from the --cui-theme-active-* pair
--cui-theme-focus-ringFocus ring color, translucent at $focus-ring-opacity

The .theme-{name} classes mirror the keys of the $theme-colors map in scss/_theme.scss — .theme-primary, .theme-success, .theme-danger, and so on — so adding or renaming a theme color automatically produces a matching class. Each entry defines the tokens for both light and dark mode via light-dark(). Themeable React components render the class through their theme prop — see the Theme page.

To see how a component consumes these tokens, here’s the badge in scss/_badge.scss. Its color and background each read a theme token first, falling back to the badge’s own default:

// the token map carries only the badge's own default
--#{$prefix}badge-color: inherit,
--#{$prefix}badge-bg: var(--#{$prefix}bg-2),

// the theme is read at the property, with the token as the fallback
color: var(--#{$prefix}theme-contrast, var(--#{$prefix}badge-color));
background-color: var(--#{$prefix}theme-bg, var(--#{$prefix}badge-bg));

With no theme class the badge stays neutral; add .theme-success and the very same element resolves the success tokens — no .badge-success selector required. Structural variants compose with any theme:

Default Success Success Success
<span className="badge">Default</span>
<span className="badge theme-success">Success</span>
<span className="badge badge-subtle theme-success">Success</span>
<span className="badge badge-outline theme-success">Success</span>
<span className="badge">Default</span>
<span className="badge theme-success">Success</span>
<span className="badge badge-subtle theme-success">Success</span>
<span className="badge badge-outline theme-success">Success</span>

Because the theme tokens are plain CSS variables, you can also scope a theme to a container and let everything inside inherit it, or define a brand of your own without recompiling:

.my-brand {
  --cui-theme-bg: #6f42c1;
  --cui-theme-contrast: #fff;
}

Where a classic color class still exists — .btn-primary, .alert-success — it is generated from the same theme tokens, so both APIs stay pixel-identical. The badge is the exception: it has no .badge-{color} spelling, and .theme-{color} is the only way to color it.

Building a themeable component

Read the theme slot at the property, wrapping the component’s own token. Slots inherit, so one form covers every case — the class works on the component, on an ancestor, or on any part of it:

--#{$prefix}badge-bg: var(--#{$prefix}bg-2),
// …
background-color: var(--#{$prefix}theme-bg, var(--#{$prefix}badge-bg));

A structural variant maps the token to a slot of its own, and then re-declares the property with a plain read so it wins over the base rule:

.badge-subtle {
  --#{$prefix}badge-bg: var(--#{$prefix}theme-bg-subtle);

  background-color: var(--#{$prefix}badge-bg);
}

The second line is the part that is easy to miss. Set the token alone and the base rule still applies its own slot, so .badge-subtle and .badge-outline collapse into the solid theme color.

A validation state works the same way: .is-invalid sets the checkbox’s tokens and re-declares background-color and border-color on :checked, so a red invalid checkbox stays red inside a themed form.

While no theme class is present the token behaves as an override point; under one, the theme wins. That keeps a token set for the whole page (.alert { --cui-alert-bg: … } in your stylesheet, or $alert-tokens in Sass) from switching the themes off. To recolor a single themed component, set the slot instead of the component token:

<CAlert theme="danger" style={{ '--cui-theme-bg-subtle': '#fde2e4' }}>…</CAlert>

On an element with a variant class it works the other way. .btn-solid and the other button variants, .alert-solid, .badge-subtle, .badge-outline, .avatar-subtle and .chip-outline map the component tokens to their own slots and paint from the tokens, so there a component token set on the element, or for the whole page in your stylesheet, wins over the theme.

Backdrop

Every overlay that dims the page — the dialog and the drawer through their native ::backdrop — paints that layer from three tokens on :root:

scss
--cui-backdrop-color: var(--cui-black),
--cui-backdrop-opacity: 50%,
--cui-backdrop-blur: 8px,

The colour and the opacity are mixed on the element (color-mix(in oklch, var(--cui-backdrop-color) var(--cui-backdrop-opacity), transparent)), so either can be changed alone, globally on :root or on one overlay. The dialog keeps the defaults, a dark scrim that deepens in the dark scheme; the drawer sets its own tint of the page (--cui-bg-body at 25%). The backdrop helper picks either look — or no backdrop at all — from the markup.

Translucent

The *-translucent variants — .navbar-translucent, .toast-translucent, .card-translucent, .drawer-translucent and .menu-translucent — let the page show through a blurred, saturated surface. All five mix the component’s own background with one opacity and apply one filter, both tokens on :root:

scss
--cui-translucent-opacity: 80%,
--cui-translucent-backdrop-filter: blur(5px) saturate(180%),

Set them on :root to retune every translucent surface at once, or on one element to change just that one — style={{ '--cui-translucent-opacity': '60%' }} on a card leaves the navbar alone.

Responsive

These Sass loops aren’t limited to color maps, either. You can also generate responsive variations of your components. Take for example the v5 responsive alignment classes of the dropdown, kept in coreui-legacy.css, where we mix an @each loop for the $breakpoints Sass map with a media query include.

scss
@each $breakpoint in map.keys($breakpoints) {
  @include media-breakpoint-up($breakpoint) {
    $infix: breakpoint-infix($breakpoint, $breakpoints);

    .dropdown-menu#{$infix}-start {
      --cui-position: start;

      &[data-coreui-popper] {
        inset-inline-start: 0;
        inset-inline-end: auto;
      }
    }

    .dropdown-menu#{$infix}-end {
      --cui-position: end;

      &[data-coreui-popper] {
        inset-inline-start: auto;
        inset-inline-end: 0;
      }
    }
  }
}

Should you modify your $breakpoints, your changes will apply to all the loops iterating over that map.

scss
$breakpoints: (
  xs: 0,
  sm: 576px,
  md: 768px,
  lg: 1024px,
  xl: 1280px,
  2xl: 1536px
) !default;

For more information and examples on how to modify our Sass maps and variables, please refer to the Sass section of the Grid documentation.

Creating your own

We encourage you to adopt these guidelines when building with CoreUI to create your own components. We’ve extended this approach ourselves to the custom components in our documentation and examples. Components like our callouts are built just like our provided components with base and modifier classes.

This is a callout. We built it custom for our docs so our messages to you stand out. It has three variants via modifier classes.
<div className="callout">...</div>
<div className="callout">...</div>

In your CSS, you’d have something like the following where the bulk of the styling is done via .callout. Then, the unique styles between each variant is controlled via modifier class.

// Base class
.callout {}

// Modifier classes
.callout-info {}
.callout-warning {}
.callout-danger {}

For the callouts, that unique styling is just a border-left-color. When you combine that base class with one of those modifier classes, you get your complete component family:

This is an info callout. Example text to show it in action.

This is a warning callout. Example text to show it in action.

This is a danger callout. Example text to show it in action.