Sass (indented syntax) best practices and coding guidelines for clean, maintainable stylesheets
Sass Best Practices
You are an expert in Sass (the indented syntax), CSS architecture, and maintainable stylesheet development.
Key Principles
Write clean, readable Sass using the indented syntax (whitespace-sensitive)
Leverage Sass features to create DRY, modular stylesheets
Maintain consistent indentation as it defines code structure
Prioritize simplicity and clarity in style organization
Sass vs SCSS
Sass uses the original indented syntax:
No curly braces {}
No semicolons ;
Indentation defines nesting
File extension: .sass
// Sass (indented syntax)
.button
display: inline-flex
padding: 8px 16px
background: #3498db
&:hover
background: darken(#3498db, 10%)
// SCSS syntax (for comparison)
.button {
display: inline-flex;
padding: 8px 16px;
background: #3498db;
&:hover {
background: darken(#3498db, 10%);
}
}
File Organization
Project Structure
sass/
├── abstracts/
│ ├── _variables.sass
│ ├── _functions.sass
│ ├── _mixins.sass
│ └── _placeholders.sass
├── base/
│ ├── _reset.sass
│ ├── _typography.sass
│ └── _base.sass
├── components/
│ ├── _buttons.sass
│ ├── _cards.sass
│ └── _forms.sass
├── layout/
│ ├── _header.sass
│ ├── _footer.sass
│ └── _grid.sass
├── pages/
│ └── _home.sass
├── themes/
│ └── _default.sass
├── vendors/
│ └── _normalize.sass
└── main.sass
Main Manifest
// main.sass
@use 'abstracts/variables'
@use 'abstracts/functions'
@use 'abstracts/mixins'
@use 'abstracts/placeholders'
@use 'vendors/normalize'
@use 'base/reset'
@use 'base/typography'
@use 'base/base'
@use 'layout/grid'
@use 'layout/header'
@use 'layout/footer'
@use 'components/buttons'
@use 'components/cards'
@use 'components/forms'
@use 'pages/home'
@use 'themes/default'
Variables
Naming and Organization
// _variables.sass
// Colors
$color-primary: #3498db
$color-primary-light: lighten($color-primary, 15%)
$color-primary-dark: darken($color-primary, 15%)
$color-secondary: #2ecc71
$color-text: #333333
$color-text-muted: #666666
$color-background: #ffffff
$color-border: #e0e0e0
$color-error: #e74c3c
$color-success: #27ae60
$color-warning: #f39c12
// Typography
$font-family-base: 'Helvetica Neue', Arial, sans-serif
$font-family-heading: 'Georgia', serif
$font-size-base: 1rem
$font-size-small: 0.875rem
$font-size-large: 1.25rem
$font-weight-normal: 400
$font-weight-bold: 700
$line-height-base: 1.5
// Spacing Scale
$spacing-unit: 8px
$spacing-xs: $spacing-unit * 0.5
$spacing-sm: $spacing-unit
$spacing-md: $spacing-unit * 2
$spacing-lg: $spacing-unit * 3
$spacing-xl: $spacing-unit * 4
$spacing-xxl: $spacing-unit * 6
// Breakpoints
$breakpoint-sm: 576px
$breakpoint-md: 768px
$breakpoint-lg: 992px
$breakpoint-xl: 1200px
// Z-index Scale
$z-index-dropdown: 1000
$z-index-sticky: 1020
$z-index-fixed: 1030
$z-index-modal: 1050
$z-index-tooltip: 1070
// Transitions
$transition-base: 0.3s ease
$transition-fast: 0.15s ease
$transition-slow: 0.5s ease
// Border Radius
$border-radius-sm: 2px
$border-radius-md: 4px
$border-radius-lg: 8px
$border-radius-pill: 50px
// Shadows
$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05)
$shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1)
$shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1)
Using Maps
// Define maps for grouped values
$colors: ("primary": #3498db, "secondary": #2ecc71, "danger": #e74c3c, "warning": #f39c12, "success": #27ae60)
$breakpoints: ("sm": 576px, "md": 768px, "lg": 992px, "xl": 1200px)
// Access values
.element
color: map-get($colors, "primary")
Mixins
Responsive Design
// _mixins.sass
=respond-to($breakpoint)
@if map-has-key($breakpoints, $breakpoint)
@media (min-width: map-get($breakpoints, $breakpoint))
@content
@else
@warn "Unknown breakpoint: #{$breakpoint}"
// Usage
.element
width: 100%
+respond-to("md")
width: 50%
+respond-to("lg")
width: 33.333%
Flexbox Utilities
=flex-center
display: flex
align-items: center
justify-content: center
=flex-between
display: flex
align-items: center
justify-content: space-between
=flex-column
display: flex
flex-direction: column
// Usage
.container
+flex-center
min-height: 100vh
Typography
=font-size($size, $line-height: null)
font-size: $size
@if $line-height
line-height: $line-height
=truncate($lines: 1)
@if $lines == 1
white-space: nowrap
overflow: hidden
text-overflow: ellipsis
@else
display: -webkit-box
-webkit-line-clamp: $lines
-webkit-box-orient: vertical
overflow: hidden
// Usage
.title
+font-size(2rem, 1.2)
.description
+truncate(3)
Accessibility
=visually-hidden
position: absolute
width: 1px
height: 1px
padding: 0
margin: -1px
overflow: hidden
clip: rect(0, 0, 0, 0)
white-space: nowrap
border: 0
=focus-visible
&:focus-visible
outline: 2px solid $color-primary
outline-offset: 2px
// Usage
.sr-only
+visually-hidden
.interactive
+focus-visible
Button Styles
=button-base
display: inline-flex
align-items: center
justify-content: center
padding: $spacing-sm $spacing-md
border: none
border-radius: $border-radius-md
font-family: inherit
font-size: $font-size-base
font-weight: $font-weight-bold
text-decoration: none
cursor: pointer
transition: all $transition-base
&:disabled
opacity: 0.5
cursor: not-allowed
=button-variant($bg-color, $text-color: white)
+button-base
background: $bg-color
color: $text-color
&:hover:not(:disabled)
background: darken($bg-color, 10%)
&:active:not(:disabled)
background: darken($bg-color, 15%)
// Usage
.btn-primary
+button-variant($color-primary)
.btn-secondary
+button-variant($color-secondary)
.btn-danger
+button-variant($color-error)
BEM Naming Convention
// Block
.card
background: $color-background
border-radius: $border-radius-md
box-shadow: $shadow-md
// Element
&__header
padding: $spacing-md
border-bottom: 1px solid $color-border
&__title
margin: 0
font-size: $font-size-large
font-weight: $font-weight-bold
&__body
padding: $spacing-md
&__footer
padding: $spacing-md
border-top: 1px solid $color-border
// Modifier
&--featured
border: 2px solid $color-primary
&--compact
.card__header,
.card__body,
.card__footer
padding: $spacing-sm
Nesting Guidelines
Keep Nesting Shallow
// BAD: Too deep
.nav
.nav-list
.nav-item
.nav-link
.nav-icon
// 5 levels - avoid this
// GOOD: Flat BEM structure
.nav
display: flex
.nav__list
display: flex
list-style: none
margin: 0
padding: 0
.nav__item
margin: 0 $spacing-sm
.nav__link
color: $color-text
text-decoration: none
&:hover,
&:focus
color: $color-primary
&--active
color: $color-primary
font-weight: $font-weight-bold
Acceptable Nesting
.component
// Pseudo-elements
&::before,
&::after
content: ''
// States
&:hover,
&:focus,
&:active
// State styles
// Modifiers
&--variant
// Modifier styles
// Media queries
+respond-to("md")
// Responsive styles
Functions
// _functions.sass
// Color functions
@function tint($color, $percentage)
@return mix(white, $color, $percentage)
@function shade($color, $percentage)
@return mix(black, $color, $percentage)
// Unit conversion
@function px-to-rem($px, $base: 16)
@return ($px / $base) * 1rem
@function rem-to-px($rem, $base: 16)
@return ($rem / 1rem) * $base * 1px
// Spacing helper
@function spacing($multiplier)
@return $spacing-unit * $multiplier
// Usage
.element
background: tint($color-primary, 20%)
border-color: shade($color-primary, 10%)
font-size: px-to-rem(18)
padding: spacing(3)
Placeholders and Extend
// Define placeholders
%clearfix
&::after
content: ''
display: table
clear: both
%list-reset
list-style: none
margin: 0
padding: 0
%button-reset
appearance: none
background: none
border: none
padding: 0
font: inherit
cursor: pointer
// Usage
.container
@extend %clearfix
.nav-list
@extend %list-reset
.icon-button
@extend %button-reset
Loops and Iteration
Generate Utility Classes
// Spacing utilities
$directions: ("": "", "t": "-top", "r": "-right", "b": "-bottom", "l": "-left")
@each $abbr, $dir in $directions
@for $i from 0 through 8
.m#{$abbr}-#{$i}
margin#{$dir}: spacing($i)
.p#{$abbr}-#{$i}
padding#{$dir}: spacing($i)
// Color utilities
@each $name, $color in $colors
.text-#{$name}
color: $color
.bg-#{$name}
background-color: $color
Grid Generation
$grid-columns: 12
@for $i from 1 through $grid-columns
.col-#{$i}
width: percentage($i / $grid-columns)
// Responsive columns
@each $bp, $width in $breakpoints
@media (min-width: $width)
@for $i from 1 through $grid-columns
.col-#{$bp}-#{$i}
width: percentage($i / $grid-columns)
Conditionals
=theme-button($variant)
@if $variant == "primary"
background: $color-primary
color: white
@else if $variant == "secondary"
background: transparent
color: $color-primary
border: 2px solid $color-primary
@else if $variant == "danger"
background: $color-error
color: white
@else
background: $color-text-muted
color: white
.btn-primary
+theme-button("primary")
.btn-secondary
+theme-button("secondary")
Modern Module System
Using @use and @forward
// _variables.sass
$primary: #3498db
// _mixins.sass
@use 'variables' as vars
=themed-element
color: vars.$primary
// _index.sass (barrel file)
@forward 'variables'
@forward 'mixins'
// main.sass
@use 'abstracts'
.element
+abstracts.themed-element
Performance Tips
Keep selector specificity low (prefer single class selectors)
Avoid !important except for utility overrides
Use @use instead of deprecated @import
Limit @extend usage across files
Compile without source maps in production
Let autoprefixer handle vendor prefixes
Code Style Guidelines
Use 2 spaces for indentation (critical in Sass)
Use single quotes for strings
One blank line between rule sets
Group related properties together
Comment non-obvious code
Use meaningful variable names
Keep lines under 80 characters when possible
Property Order
.element
// Positioning
position: relative
top: 0
right: 0
z-index: 10
// Display & Box Model
display: flex
width: 100%
padding: $spacing-md
margin: $spacing-sm 0
// Typography
font-family: $font-family-base
font-size: $font-size-base
line-height: $line-height-base
color: $color-text
// Visual
background: $color-background
border: 1px solid $color-border
border-radius: $border-radius-md
box-shadow: $shadow-sm
// Animation
transition: all $transition-base
// Misc
cursor: pointerdon't have the plugin yet? install it then click "run inline in claude" again.