Styling
-
Keep styles scoped to the component or feature that defines them. Most O3 modules import colocated Sass files such as
user.scss; use.module.scssonly in packages that intentionally use CSS Modules.// user.scss .container { display: flex; gap: 1rem; }// user.component.tsx import styles from './user.scss'; return <div className={styles.container}>...</div>; -
Avoid deep nesting of styles as it increases specificity and makes styles harder to override. Prefer flat selectors where possible:
// ❌ Bad: Deep nesting .container { .header { .title { .text { color: colors.$gray-100; } } } } // ✅ Good: Flat structure .container { display: flex; } .headerTitle { color: colors.$gray-100; } -
Avoid using global styles as they can cause unintended side effects and make the codebase harder to maintain. Instead, scope style overrides under specific class names:
// ❌ Bad: Global styles affecting all text inputs :global(.cds--text-input) { height: 3rem; @extend .label01; } // ✅ Good: Styles scoped to a specific component .patientSearchInput { :global(.cds--text-input) { height: 3rem; @extend .label01; } // Additional component-specific styles display: flex; gap: 1rem; }Benefits of scoped styles:
- Prevents unintended style leaks
- Makes it clear which component owns the styles
- Easier to maintain and debug
- Reduces the risk of style conflicts
- Better encapsulation of component styles
-
When you do need to override Carbon Design System styles:
- First try using Carbon’s built-in props and variants
- If that’s not possible, scope the override to your component’s class
- Document why the override is necessary with a comment
- If you need to override styles across multiple components, consider adding an explicit override to the
_overrides.scssfile. This approach is explained in the next tip.
.formField { // Override Carbon's default spacing for dense forms :global(.cds--form-item) { margin-bottom: 0.5rem; } // Custom styling for validation states &.hasError { :global(.cds--text-input) { border-color: $danger; } } } -
Put Carbon style overrides in _overrides.scss . This ensures that the overrides are applied consistently across the application.
-
Prefer using Carbon color , spacing and type tokens over hard-coded values. Below are some examples of using tokens in code:
@use "@carbon/colors"; @use "@carbon/layout"; @use "@carbon/type"; .listWrapper { margin: layout.$spacing-05; // 1rem } .resultsCount { @include type.type-style("label-01"); } .sortDropdown { color: colors.$gray-100; gap: 0; }Find a useful reference for color token mappings here .
-
Use SASS features like interpolation, at-rules, mixins, and functions to make your styles more reusable and maintainable.
-
If you want to apply styles based on the user’s viewport size, use our predefined breakpoints . For example, to apply different styles for tablet and desktop viewports, do this:
// Tablet viewports :global(.omrs-breakpoint-lt-desktop) { .form { height: calc(100vh - 9rem); } } // Desktop viewports :global(.omrs-breakpoint-gt-tablet) { .form { height: calc(100vh - 6rem); } }ℹ️Make sure to scope your styles under a class name (such as
.formin the example above) to avoid them affecting other components. -
Use the classnames library to conditionally apply styles to an element, adding it to the package’s dependencies first if the module does not already declare it. Consider using classnames if you’re interpolating multiple class names into a string. For example, the following snippet:
<NumberInput allowEmpty className={`${styles.textInput} ${val.className}`} // other props omitted for brevity />Could be replaced by:
import classNames from "classnames"; <NumberInput allowEmpty className={classNames(styles.textInput, val.className)} // ... other props omitted for brevity />;The following snippet shows a more advanced case - a
divstyled with multiple conditional styles:return ( <div className={`${styles.textInputContainer} ${disabled && styles.disabledInput} ${ !isWithinNormalRange && styles.danger } ${useMuacColors ? muacColorCode : undefined}`} > // ... details omitted for brevity </div> );You can refactor this snippet to leverage
classnamesas follows:import classNames from "classnames"; const containerClasses = classNames(styles.textInputContainer, { [styles.disabledInput]: disabled, [styles.danger]: !isWithinNormalRange, [muacColorCode]: useMuacColors, }); return <div className={containerClasses}>// ... details omitted for brevity</div>; -
Be wary of using newer CSS features that don’t have widespread browser support. While modern CSS features can be powerful, we need to ensure our applications work across all supported browsers. Here are some guidelines:
-
Check browser support on Can I Use before using newer CSS features
-
Consider providing fallbacks for newer features:
.container { // Fallback for browsers that don't support gap margin-right: 1rem; // Modern browsers will use gap gap: 1rem; } -
Be especially careful with features that are still partial or changing, such as new container query variants, CSS anchor positioning, view transitions, or other features that have not reached broad support in the browsers listed by
browserslist-config-openmrs. -
Prefer well-supported alternatives when possible:
- Use flexbox/grid for core layouts unless a newer feature materially simplifies the component
- Use @media queries or size container queries where they fit the responsive behavior
- Use
@supportsguards and fallbacks for progressive enhancements
-
Test your styles across our supported browsers:
- Chrome/Edge (latest versions)
- Firefox (latest version)
- Safari (latest version)
-