rule-frontend-styles
MANDATORY when editing files matching ["frontend/src/styles/**/*.scss", "frontend/src/**/*.module.scss", "frontend/src/**/*.scss"]. SCSS styling patterns for the Simpler Grants frontend — USWDS theme
SKILL.md.claude/skills/rule-frontend-styles/SKILL.md
GitHubFrontend Styles Rules
Scope
This rule covers SCSS, USWDS theme tokens, breakpoints, and print styles. Component structure, JSX, and USWDS class usage in markup live in frontend-components.mdc.
File Organization
ALL global SCSS lives in frontend/src/styles/ as a flat structure — no subfolders today. The entry point styles.scss uses modern Sass @forward (NEVER legacy @import) in this exact dependency order:
// styles.scss
@forward "uswds-theme";
@forward "uswds";
@forward "uswds-theme-custom-styles";
@forward "loading";
- Token overrides →
_uswds-theme.scss - Custom styles, mixins, print rules →
_uswds-theme-custom-styles.scss - Loading state →
_loading.scss
ALWAYS insert new partials in dependency order and add them to styles.scss. WHEN _uswds-theme-custom-styles.scss grows past ~500 lines, extract cohesive sections (e.g., _print.scss, _mixins.scss) and @forward them in order.
USWDS Theme Overrides (_uswds-theme.scss)
ALWAYS override via USWDS Sass settings variables. NEVER hard-code hex.
$theme-color-primary-family: "mint";
$theme-respect-user-font-size: true;
The project's color families are fixed — new components MUST consume these and NEVER introduce ad-hoc palettes:
| Role | Family |
|---|---|
| base | gray-warm |
| primary | mint |
| secondary | blue-cool-vivid |
| accent-warm | yellow / orange-vivid |
| accent-cool | violet |
Other fixed tokens:
- Fonts: Public Sans (sans), Newsreader (serif), variable weights 200–800
- Type scale: tokens 1–17
- Container:
desktop-lgmax-width - Column gaps:
4mobile /6desktop - Link colors:
mint-60/mint-70, visitedviolet-60 - Button radius:
2px - Tooltip:
blue-cool-60v - Identifier bg:
mint-70
REUSE these tokens rather than redefining gutters, fonts, or colors. When adding a new override, group it with the matching USWDS settings section and leave a comment linking to the USWDS settings docs.
Custom Styles & Mixins (_uswds-theme-custom-styles.scss)
PREFER USWDS mixins/functions already in use here:
- Mixins:
u-shadow(),u-padding-right(),at-media(),at-media-max(),button-unstyled,z-index() - Functions:
units(),color(),family()
REUSE established class patterns:
.usa-button--{variant}for button variants.simpler-{thing}for project-specific utilities (e.g.,.simpler-responsive-table)- BEM-ish suffixes:
__element,--modifier(e.g.,.sf424__table,.saved-opportunity-*)
DO NOT invent new mixins inline. If logic repeats 3+ times, factor it out — and once you have several mixins, extract a _mixins.scss partial.
Responsive Breakpoints
ALWAYS use @include at-media($breakpoint) / at-media-max($breakpoint) with USWDS named breakpoints: mobile-lg, tablet, desktop, desktop-lg. NEVER write raw @media (min-width: ...).
.pdf-card {
padding: units(2);
@include at-media('tablet') {
padding: units(4);
}
@include at-media('desktop') {
@include u-shadow(2);
}
}
- Mobile-first: base styles, then
at-media('tablet'), thendesktop. widescreenutilities are disabled in this project — DO NOT target it.
Print Styles
Print rules currently live alongside their component in _uswds-theme-custom-styles.scss — see .sf424__table for the canonical example. Follow that pattern.
In @media print blocks:
- Hide nav, footer, and interactive controls
- Force black-on-white text
- Expand table layouts so all columns fit
- Use
page-break-inside: avoidon key blocks - Avoid background images
- Ensure form data is fully visible
.sf424__table {
@media print {
display: table;
width: 100%;
page-break-inside: avoid;
}
}
WHEN multiple components need print rules, propose extracting a _print.scss partial and @forward it from styles.scss after _uswds-theme-custom-styles.scss.
CSS Modules (*.module.scss)
- Co-locate next to the component:
Component.module.scssbesideComponent.tsx. - Use camelCase class names; access via
styles.className. - Modules MUST still consume USWDS tokens via
@use "styles/uswds-theme" as *;(or the project's established@usepath) — NEVER hard-code colors or breakpoints. - Keep modules small. PREFER USWDS utility classes in JSX for one-offs.
Note: at the time of writing, the codebase uses primarily global SCSS + USWDS utility classes in JSX. CSS Modules are acceptable for genuinely component-local styling, but check for an existing pattern first.
Accessibility-Related Styling
- PRESERVE the WCAG AA contrast fixes already applied to nav — do not regress them.
- NEVER remove focus outlines without an equivalent visible replacement.
- RESPECT
prefers-reduced-motionfor any new animation (e.g., the existing.pdf-cardhover transform).
.pdf-card {
transition: transform 150ms ease;
@media (prefers-reduced-motion: reduce) {
transition: none;
}
}
Anti-Patterns
- NO legacy
@import— use@use/@forward. - NO hard-coded hex or
pxwhen a USWDS token exists — usecolor('mint-60'),units(2). - NO raw media queries — always
at-media. - NO targeting
widescreen(disabled). - NO nesting deeper than 3 levels.
- NO
!importantexcept to override third-party styles, with an inline comment explaining why. - NO new global styles for component-specific concerns — use a CSS Module or add a clearly-scoped block in
_uswds-theme-custom-styles.scss.
Related Rules
frontend-components.mdc— component structure, JSX, USWDS class usage in markupfrontend-i18n.mdc— translated strings consumed in styled componentsaccessibility.mdc— WCAG 2.1 AA compliance (contrast, focus, motion)
Specialist Validation
Simple changes (token tweak, new variant class): No specialist needed.
Moderate changes (new partial, new mixin, print rules for a new component): Invoke codebase-conventions-reviewer.
Complex changes (USWDS upgrade, new color family, restructuring styles/): Invoke in parallel:
architecture-strategist— validate partial structure and@forwardorderaccessibility-auditor— verify contrast, focus, and reduced-motion complianceperformance-oracle— check CSS bundle impact