Instruction file imported from kubevirt-ui/kubevirt-plugin (
.cursor/rules/styles.mdc). Copyright stays with the author.
SCSS Styling Guidelines
Styling conventions and patterns for SCSS files in this project.
1. File Organization
File Naming
- Match component:
VMDetails.tsx→vm-details.scss - Lowercase kebab-case
- Place in same directory as component
File Structure
// 1. Variables (if component-specific)
$vm-details-header-height: 60px;
// 2. Main block
.vm-details {
// Layout
// Spacing
// Typography
// Colors
// 3. Elements (BEM)
&__header { }
&__content { }
&__footer { }
// 4. Modifiers (BEM)
&--loading { }
&--compact { }
// 5. States
&:hover { }
&:focus { }
// 6. Responsive
@media (max-width: 768px) { }
}
2. BEM Methodology
Block
- Component's root class
- Lowercase kebab-case
- Unique, descriptive name
Element
- Child parts of block
- Double underscore separator:
&__element - Describes purpose, not appearance
Modifier
- Variations of block/element
- Double hyphen separator:
&--modifier - Describes state or variant
Examples
// Block
.vm-list {
display: flex;
flex-direction: column;
// Elements
&__header {
display: flex;
justify-content: space-between;
}
&__item {
padding: var(--pf-t--global--spacer--md);
border-bottom: 1px solid var(--pf-t--global--border--color--default);
}
&__empty-state {
text-align: center;
padding: var(--pf-t--global--spacer--xl);
}
// Modifiers
&--compact {
.vm-list__item {
padding: var(--pf-t--global--spacer--sm);
}
}
&--loading {
opacity: 0.6;
pointer-events: none;
}
}
3. PatternFly Variables
Use PatternFly tokens. All tokens use the --pf-t prefix. Full reference: https://www.patternfly.org/tokens/all-patternfly-tokens
Spacing
// Standard spacers
padding: var(--pf-t--global--spacer--xs); // 0.25rem (4px)
padding: var(--pf-t--global--spacer--sm); // 0.5rem (8px)
padding: var(--pf-t--global--spacer--md); // 1rem (16px)
padding: var(--pf-t--global--spacer--lg); // 1.5rem (24px)
padding: var(--pf-t--global--spacer--xl); // 2rem (32px)
padding: var(--pf-t--global--spacer--2xl); // 3rem (48px)
padding: var(--pf-t--global--spacer--3xl); // 4rem (64px)
padding: var(--pf-t--global--spacer--4xl); // 5rem (80px)
// Semantic gap tokens
gap: var(--pf-t--global--spacer--gap--text-to-element--default); // 0.5rem
gap: var(--pf-t--global--spacer--gap--group--vertical); // 0.5rem
gap: var(--pf-t--global--spacer--gap--group--horizontal); // 1rem
gap: var(--pf-t--global--spacer--gap--action-to-action--default); // 1rem
gap: var(--pf-t--global--spacer--gap--group-to-group--vertical--default); // 1.5rem
Colors
// Text colors
color: var(--pf-t--global--text--color--regular); // Primary text
color: var(--pf-t--global--text--color--subtle); // Secondary text
color: var(--pf-t--global--text--color--placeholder); // Placeholder text
color: var(--pf-t--global--text--color--disabled); // Disabled text
color: var(--pf-t--global--text--color--inverse); // Text on inverse backgrounds
// Status colors
color: var(--pf-t--global--color--status--success--default); // Success/green
color: var(--pf-t--global--color--status--warning--default); // Warning/yellow
color: var(--pf-t--global--color--status--danger--default); // Danger/red
color: var(--pf-t--global--color--status--info--default); // Info/purple
color: var(--pf-t--global--color--status--custom--default); // Custom/teal
// Brand
color: var(--pf-t--global--color--brand--default);
// Background
background-color: var(--pf-t--global--background--color--primary--default);
background-color: var(--pf-t--global--background--color--secondary--default);
background-color: var(--pf-t--global--background--color--floating--default);
background-color: var(--pf-t--global--background--color--disabled--default);
// Borders
border-color: var(--pf-t--global--border--color--default);
border-color: var(--pf-t--global--border--color--hover);
// Links
color: var(--pf-t--global--text--color--link--default);
color: var(--pf-t--global--text--color--link--hover);
color: var(--pf-t--global--text--color--link--visited);
Typography
// Font sizes
font-size: var(--pf-t--global--font--size--xs); // 0.75rem (12px)
font-size: var(--pf-t--global--font--size--sm); // 0.875rem (14px)
font-size: var(--pf-t--global--font--size--md); // 1rem (16px)
font-size: var(--pf-t--global--font--size--lg); // 1.125rem (18px)
font-size: var(--pf-t--global--font--size--xl); // 1.25rem (20px)
font-size: var(--pf-t--global--font--size--2xl); // 1.5rem (24px)
// Body font sizes
font-size: var(--pf-t--global--font--size--body--sm); // 0.75rem (12px)
font-size: var(--pf-t--global--font--size--body--default); // 0.875rem (14px)
font-size: var(--pf-t--global--font--size--body--lg); // 1rem (16px)
// Heading font sizes
font-size: var(--pf-t--global--font--size--heading--h1); // 1.5rem
font-size: var(--pf-t--global--font--size--heading--h2); // 1.25rem
font-size: var(--pf-t--global--font--size--heading--h3); // 1.125rem
// Font weights
font-weight: var(--pf-t--global--font--weight--body--default); // 400
font-weight: var(--pf-t--global--font--weight--body--bold); // 500
font-weight: var(--pf-t--global--font--weight--heading--default); // 500
font-weight: var(--pf-t--global--font--weight--heading--bold); // 700
// Line heights
line-height: var(--pf-t--global--font--line-height--heading); // 1.3
line-height: var(--pf-t--global--font--line-height--body); // 1.5
// Font families
font-family: var(--pf-t--global--font--family--body);
font-family: var(--pf-t--global--font--family--heading);
font-family: var(--pf-t--global--font--family--mono);
Borders & Shadows
// Border widths
border-width: var(--pf-t--global--border--width--regular); // 1px
border-width: var(--pf-t--global--border--width--strong); // 2px
border-width: var(--pf-t--global--border--width--extra-strong); // 3px
// Border radius
border-radius: var(--pf-t--global--border--radius--sharp); // 0px
border-radius: var(--pf-t--global--border--radius--tiny); // 4px
border-radius: var(--pf-t--global--border--radius--small); // 6px
border-radius: var(--pf-t--global--border--radius--medium); // 16px
border-radius: var(--pf-t--global--border--radius--large); // 24px
border-radius: var(--pf-t--global--border--radius--pill); // 999px
// Box shadows (composed from individual tokens)
box-shadow:
var(--pf-t--global--box-shadow--X--sm--default)
var(--pf-t--global--box-shadow--Y--sm--default)
var(--pf-t--global--box-shadow--blur--sm)
var(--pf-t--global--box-shadow--spread--sm--default)
var(--pf-t--global--box-shadow--color--sm--default);
Z-Index
z-index: var(--pf-t--global--z-index--xs); // 100 - box shadows, hovered elements
z-index: var(--pf-t--global--z-index--sm); // 200 - menus, dropdowns
z-index: var(--pf-t--global--z-index--md); // 300 - sticky elements, banners
z-index: var(--pf-t--global--z-index--lg); // 400 - backdrop
z-index: var(--pf-t--global--z-index--xl); // 500 - modals
z-index: var(--pf-t--global--z-index--2xl); // 600 - toast alerts (topmost)
4. Responsive Design
Breakpoints
// Mobile first approach
.component {
// Mobile styles (default)
padding: var(--pf-t--global--spacer--sm);
// Tablet and up
@media (min-width: 768px) {
padding: var(--pf-t--global--spacer--md);
}
// Desktop and up
@media (min-width: 1024px) {
padding: var(--pf-t--global--spacer--lg);
}
}
Responsive Utilities
.hide-on-mobile {
@media (max-width: 767px) {
display: none;
}
}
.hide-on-desktop {
@media (min-width: 768px) {
display: none;
}
}
5. Best Practices
Do
// ✅ Use PatternFly tokens
padding: var(--pf-t--global--spacer--md);
// ✅ Use relative units
font-size: 1rem;
width: 100%;
// ✅ Use project-specific class names
.vm-list-item { }
// ✅ Use gap for spacing in flex/grid
display: flex;
gap: var(--pf-t--global--spacer--md);
// ✅ Use logical properties
margin-inline-start: var(--pf-t--global--spacer--sm);
padding-block: var(--pf-t--global--spacer--md);
Don't
// ❌ Don't use !important
.my-class {
color: red !important;
}
// ❌ Don't target PatternFly classes
.pf-v6-c-button { }
// ❌ Don't use px for sizing
padding: 16px;
// ❌ Don't use hardcoded colors
color: #333333;
// ❌ Don't deeply nest (max 3 levels)
.a {
.b {
.c {
.d { } // Too deep
}
}
}
// ❌ Don't use IDs for styling
#my-component { }
// ❌ Don't use old --pf-v6-global-- variables (use --pf-t--global-- tokens)
padding: var(--pf-v6-global--spacer--md);
6. Dark Mode Support
Using CSS Variables
// PatternFly tokens automatically adapt to dark mode
// Just use the tokens consistently
.component {
background-color: var(--pf-t--global--background--color--primary--default);
color: var(--pf-t--global--text--color--regular);
border-color: var(--pf-t--global--border--color--default);
}
// Avoid hardcoded colors
// ❌ color: #333;
// ✅ color: var(--pf-t--global--text--color--regular);
7. Checklist
- Uses PatternFly
--pf-t--tokens for spacing, colors, typography - Follows BEM naming convention
- No
!importantusage - No PatternFly class targeting
- No hardcoded colors or sizes
- Maximum 3 levels of nesting
- Responsive considerations included
- Supports dark mode via CSS variables