Reusable Utility Classes
Include these in any LWC component's CSS file. Each class maps directly to SLDS 2 hooks.
Layout Utilities
.util-stack {
display: flex;
flex-direction: column;
gap: var(--slds-g-spacing-4, 1rem);
}
.util-stack--tight { gap: var(--slds-g-spacing-2, 0.5rem); }
.util-stack--loose { gap: var(--slds-g-spacing-6, 1.5rem); }
.util-cluster {
display: flex;
flex-wrap: wrap;
gap: var(--slds-g-spacing-3, 0.75rem);
align-items: center;
}
.util-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.util-center {
display: flex;
align-items: center;
justify-content: center;
}
.util-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--slds-g-spacing-4, 1rem);
}
.util-grid--2col { grid-template-columns: repeat(2, 1fr); }
.util-grid--3col { grid-template-columns: repeat(3, 1fr); }
.util-grid--4col { grid-template-columns: repeat(4, 1fr); }
Surface Utilities
.util-surface {
background: var(--slds-g-color-surface-1, #ffffff);
color: var(--slds-g-color-on-surface-1, #181818);
}
.util-surface--muted {
background: var(--slds-g-color-surface-container-1, #f8f8f8);
color: var(--slds-g-color-on-surface-1, #181818);
}
.util-surface--raised {
background: var(--slds-g-color-surface-1, #ffffff);
box-shadow: var(--slds-g-shadow-2);
}
.util-surface--sunken {
background: var(--slds-g-color-surface-container-2, #f3f3f3);
}
Text Utilities
.util-text--heading {
font-size: var(--slds-g-font-size-7, 1.25rem);
font-weight: var(--slds-g-font-weight-7, 700);
color: var(--slds-g-color-on-surface-1, #181818);
line-height: var(--slds-g-line-height-2, 1.25);
}
.util-text--subheading {
font-size: var(--slds-g-font-size-4, 0.875rem);
font-weight: var(--slds-g-font-weight-6, 600);
color: var(--slds-g-color-on-surface-1, #181818);
}
.util-text--body {
font-size: var(--slds-g-font-size-4, 0.875rem);
color: var(--slds-g-color-on-surface-1, #181818);
line-height: var(--slds-g-line-height-4, 1.5);
}
.util-text--caption {
font-size: var(--slds-g-font-size-2, 0.75rem);
color: var(--slds-g-color-on-surface-2, #444444);
}
.util-text--muted {
color: var(--slds-g-color-on-surface-3, #706e6b);
}
.util-text--truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
Component Recipes
Card
Shadcn-inspired card with header, content, and footer slots.
<class="code-tag">template>
<class="code-tag">div class={cardClass}>
<class="code-tag">div class="recipe-card__header" if:true={title}>
<class="code-tag">h3 class="recipe-card__title">{title}</class="code-tag">h3>
<class="code-tag">div class="recipe-card__actions">
<class="code-tag">slot name="actions"></class="code-tag">slot>
</class="code-tag">div>
</class="code-tag">div>
<class="code-tag">div class="recipe-card__body">
<class="code-tag">slot></class="code-tag">slot>
</class="code-tag">div>
<class="code-tag">div class="recipe-card__footer" if:true={hasFooter}>
<class="code-tag">slot name="footer"></class="code-tag">slot>
</class="code-tag">div>
</class="code-tag">div>
</class="code-tag">template>
.recipe-card {
background: var(--slds-g-color-surface-1, #ffffff);
border: var(--slds-g-sizing-border-1) solid var(--slds-g-color-border-1, #e5e5e5);
border-radius: var(--slds-g-radius-border-3, 0.5rem);
overflow: hidden;
}
.recipe-card--elevated {
box-shadow: var(--slds-g-shadow-2);
border: none;
}
.recipe-card__header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--slds-g-spacing-4, 1rem) var(--slds-g-spacing-4, 1rem) 0;
}
.recipe-card__title {
font-size: var(--slds-g-font-size-5, 1rem);
font-weight: var(--slds-g-font-weight-6, 600);
color: var(--slds-g-color-on-surface-1, #181818);
margin: 0;
}
.recipe-card__body {
padding: var(--slds-g-spacing-4, 1rem);
}
.recipe-card__footer {
padding: var(--slds-g-spacing-3, 0.75rem) var(--slds-g-spacing-4, 1rem);
border-top: var(--slds-g-sizing-border-1) solid var(--slds-g-color-border-1, #e5e5e5);
background: var(--slds-g-color-surface-container-1, #f8f8f8);
}
Badge / Pill
.recipe-badge {
display: inline-flex;
align-items: center;
gap: var(--slds-g-spacing-1, 0.25rem);
padding: var(--slds-g-spacing-1, 0.25rem) var(--slds-g-spacing-3, 0.75rem);
font-size: var(--slds-g-font-size-2, 0.75rem);
font-weight: var(--slds-g-font-weight-5, 500);
border-radius: var(--slds-g-radius-border-pill, 9999px);
white-space: nowrap;
}
.recipe-badge--default {
background: var(--slds-g-color-surface-container-2, #f3f3f3);
color: var(--slds-g-color-on-surface-1, #181818);
}
.recipe-badge--success {
background: var(--slds-g-color-success-container-1, #e6f9e6);
color: var(--slds-g-color-success-1, #2e844a);
}
.recipe-badge--error {
background: var(--slds-g-color-error-container-1, #fef1f1);
color: var(--slds-g-color-error-1, #ea001e);
}
.recipe-badge--warning {
background: var(--slds-g-color-warning-container-1, #fef4e6);
color: var(--slds-g-color-warning-1, #dd7a01);
}
.recipe-badge--info {
background: var(--slds-g-color-info-container-1, #e5f0fb);
color: var(--slds-g-color-info-1, #0176d3);
}
Data Table with Hover
.recipe-table {
width: 100%;
border-collapse: collapse;
font-size: var(--slds-g-font-size-3, 0.8125rem);
}
.recipe-table th {
text-align: left;
padding: var(--slds-g-spacing-3, 0.75rem) var(--slds-g-spacing-4, 1rem);
font-weight: var(--slds-g-font-weight-6, 600);
color: var(--slds-g-color-on-surface-2, #444444);
background: var(--slds-g-color-surface-container-1, #f8f8f8);
border-bottom: var(--slds-g-sizing-border-2) solid var(--slds-g-color-border-1, #e5e5e5);
}
.recipe-table td {
padding: var(--slds-g-spacing-3, 0.75rem) var(--slds-g-spacing-4, 1rem);
color: var(--slds-g-color-on-surface-1, #181818);
border-bottom: var(--slds-g-sizing-border-1) solid var(--slds-g-color-border-2, #eeeeee);
}
.recipe-table tr: hover td {
background: var(--slds-g-color-surface-3, #f3f3f3);
}
.recipe-table tr: focus-within td {
background: var(--slds-g-color-accent-container-1, #e5f0fb);
}
Form Input with Focus Ring
.recipe-input {
width: 100%;
padding: var(--slds-g-spacing-2, 0.5rem) var(--slds-g-spacing-3, 0.75rem);
font-size: var(--slds-g-font-size-4, 0.875rem);
color: var(--slds-g-color-on-surface-1, #181818);
background: var(--slds-g-color-surface-1, #ffffff);
border: var(--slds-g-sizing-border-1) solid var(--slds-g-color-border-1, #e5e5e5);
border-radius: var(--slds-g-radius-border-2, 0.25rem);
transition: border-color 150ms ease, box-shadow 150ms ease;
}
.recipe-input: hover {
border-color: var(--slds-g-color-accent-2, #014486);
}
.recipe-input: focus {
outline: none;
border-color: var(--slds-g-color-accent-1, #0176d3);
box-shadow: 0 0 0 3px var(--slds-g-color-accent-container-1, #e5f0fb);
}
.recipe-input: :placeholder {
color: var(--slds-g-color-on-surface-3, #706e6b);
}
.recipe-input--error {
border-color: var(--slds-g-color-error-1, #ea001e);
}
.recipe-input--error: focus {
box-shadow: 0 0 0 3px var(--slds-g-color-error-container-1, #fef1f1);
}
Custom Button (Beyond lightning-button)
When lightning-button doesn't fit the design, use custom buttons that still respect SLDS hooks.
.recipe-btn {
display: inline-flex;
align-items: center;
gap: var(--slds-g-spacing-2, 0.5rem);
padding: var(--slds-g-spacing-2, 0.5rem) var(--slds-g-spacing-4, 1rem);
font-size: var(--slds-g-font-size-4, 0.875rem);
font-weight: var(--slds-g-font-weight-6, 600);
border-radius: var(--slds-g-radius-border-2, 0.25rem);
border: var(--slds-g-sizing-border-1) solid transparent;
cursor: pointer;
transition: background-color 150ms ease, border-color 150ms ease;
}
.recipe-btn--primary {
background: var(--slds-g-color-accent-1, #0176d3);
color: var(--slds-g-color-on-accent-1, #ffffff);
}
.recipe-btn--primary: hover {
background: var(--slds-g-color-accent-2, #014486);
}
.recipe-btn--secondary {
background: transparent;
color: var(--slds-g-color-accent-1, #0176d3);
border-color: var(--slds-g-color-border-1, #e5e5e5);
}
.recipe-btn--secondary: hover {
background: var(--slds-g-color-surface-container-1, #f8f8f8);
}
.recipe-btn--ghost {
background: transparent;
color: var(--slds-g-color-on-surface-1, #181818);
border-color: transparent;
}
.recipe-btn--ghost: hover {
background: var(--slds-g-color-surface-3, #f3f3f3);
}
.recipe-btn: focus-visible {
outline: 2px solid var(--slds-g-color-accent-1, #0176d3);
outline-offset: 2px;
}
.recipe-btn: disabled {
background: var(--slds-g-color-disabled-container-1, #ecebea);
color: var(--slds-g-color-on-disabled-1, #706e6b);
cursor: not-allowed;
border-color: transparent;
}
Modal / Dialog Overlay
.recipe-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9000;
}
.recipe-dialog {
background: var(--slds-g-color-surface-1, #ffffff);
border-radius: var(--slds-g-radius-border-3, 0.5rem);
box-shadow: var(--slds-g-shadow-4);
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.recipe-dialog__header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--slds-g-spacing-4, 1rem);
border-bottom: var(--slds-g-sizing-border-1) solid var(--slds-g-color-border-1, #e5e5e5);
}
.recipe-dialog__body {
padding: var(--slds-g-spacing-4, 1rem);
}
.recipe-dialog__footer {
display: flex;
justify-content: flex-end;
gap: var(--slds-g-spacing-3, 0.75rem);
padding: var(--slds-g-spacing-3, 0.75rem) var(--slds-g-spacing-4, 1rem);
border-top: var(--slds-g-sizing-border-1) solid var(--slds-g-color-border-1, #e5e5e5);
}
CSS Composition Patterns
Layering Hooks for Complex Effects
Combine SLDS hooks to create sophisticated visual treatments without hardcoded values.
.frosted-panel {
background: var(--slds-g-color-surface-1, #ffffff);
border: var(--slds-g-sizing-border-1) solid var(--slds-g-color-border-1, #e5e5e5);
border-radius: var(--slds-g-radius-border-3, 0.5rem);
box-shadow: var(--slds-g-shadow-3);
backdrop-filter: blur(8px);
}
.accent-bar {
height: var(--slds-g-spacing-1, 0.25rem);
background: linear-gradient(
90deg,
var(--slds-g-color-accent-1, #0176d3),
var(--slds-g-color-accent-3, #1b96ff)
);
border-radius: var(--slds-g-radius-border-pill) var(--slds-g-radius-border-pill) 0 0;
}
Status Indicator Pattern
Reusable status dot + text pattern using feedback colors.
.status-indicator {
display: inline-flex;
align-items: center;
gap: var(--slds-g-spacing-2, 0.5rem);
font-size: var(--slds-g-font-size-3, 0.8125rem);
}
.status-dot {
width: var(--slds-g-sizing-1, 0.5rem);
height: var(--slds-g-sizing-1, 0.5rem);
border-radius: var(--slds-g-radius-circle);
flex-shrink: 0;
}
.status-dot--active { background: var(--slds-g-color-success-1, #2e844a); }
.status-dot--warning { background: var(--slds-g-color-warning-1, #dd7a01); }
.status-dot--error { background: var(--slds-g-color-error-1, #ea001e); }
.status-dot--inactive { background: var(--slds-g-color-disabled-1, #c9c7c5); }