Home β€Ί Blog β€Ί How to Use Sass and SCSS with Jekyll (Complete Guide)
Tutorial

How to Use Sass and SCSS with Jekyll (Complete Guide)

A complete guide to using Sass and SCSS with Jekyll β€” directory structure, importing partials, variables, nesting, theming with custom properties, and compilation settings.

How to Use Sass and SCSS with Jekyll (Complete Guide)

Jekyll has built-in Sass processing β€” no Node.js, no build tools, no webpack required. Write SCSS files, Jekyll compiles them to CSS automatically. This guide covers everything from basic setup to advanced patterns used in production Jekyll themes.

Sass vs SCSS

Sass has two syntaxes:

SCSS (Sassy CSS) β€” superset of CSS. Valid CSS is valid SCSS. Uses curly braces and semicolons. The most common syntax.

Sass (indented syntax) β€” uses indentation instead of braces, no semicolons. Older syntax, less commonly used.

Jekyll supports both. This guide uses SCSS (the .scss extension).

Jekyll’s Sass directory structure

Jekyll processes Sass files with this convention:

  • Files in _sass/ starting with _ are partials β€” they are imported by other files, never compiled directly
  • Files in assets/css/ (or anywhere outside _sass/) with .scss extension and front matter are entry points β€” Jekyll compiles these to CSS
_sass/                     ← partials live here
β”œβ”€β”€ _variables.scss
β”œβ”€β”€ _base.scss
β”œβ”€β”€ _nav.scss
β”œβ”€β”€ _cards.scss
└── _post.scss

assets/
└── css/
    └── main.scss          ← entry point β€” compiled to main.css

Creating the entry point

The entry point file needs front matter (even if empty) to tell Jekyll to process it:

/* assets/css/main.scss */
---
---

@import "variables";
@import "base";
@import "nav";
@import "cards";
@import "post";

The empty --- block is required. Without it, Jekyll copies the file as-is without processing.

Import partials without the leading _ or .scss extension β€” Sass resolves them automatically.

SCSS partials in _sass/

_variables.scss β€” design tokens

// _sass/_variables.scss

// Colours
$color-primary:    #2563eb;
$color-primary-dark: #1d4ed8;
$color-text:       #1a1a2e;
$color-muted:      #6b7280;
$color-border:     #e5e7eb;
$color-bg:         #ffffff;
$color-bg-alt:     #f9fafb;

// Typography
$font-sans:        "Inter", system-ui, -apple-system, sans-serif;
$font-mono:        "Fira Code", "Cascadia Code", monospace;
$font-size-base:   1rem;
$line-height-base: 1.6;

// Spacing
$spacing-xs:   0.25rem;
$spacing-sm:   0.5rem;
$spacing-md:   1rem;
$spacing-lg:   1.5rem;
$spacing-xl:   2rem;
$spacing-2xl:  3rem;

// Layout
$container-max: 1200px;
$sidebar-width: 280px;

// Borders
$radius-sm:  4px;
$radius-md:  10px;
$radius-lg:  16px;
$radius-full: 9999px;

// Shadows
$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
$shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
$shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);

// Transitions
$transition-fast:   150ms ease;
$transition-base:   250ms ease;
$transition-slow:   400ms ease;

_base.scss β€” reset and global styles

// _sass/_base.scss
@import "variables";

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: $font-sans;
  font-size: $font-size-base;
  line-height: $line-height-base;
  color: $color-text;
  background: $color-bg;
}

img {
  max-width: 100%;
  height: auto;
  display: block;
}

a {
  color: $color-primary;
  text-decoration: none;

  &:hover {
    text-decoration: underline;
  }
}

h1, h2, h3, h4, h5, h6 {
  line-height: 1.3;
  font-weight: 700;
  margin-top: 0;
}

code {
  font-family: $font-mono;
  font-size: 0.875em;
  background: $color-bg-alt;
  padding: 0.15em 0.4em;
  border-radius: $radius-sm;
}

SCSS features used in Jekyll themes

Variables

$color-primary: #2563eb;

.btn {
  background: $color-primary;
  
  &:hover {
    background: darken($color-primary, 10%);
  }
}

Nesting

.card {
  border-radius: $radius-md;
  overflow: hidden;
  
  &__image {
    width: 100%;
    aspect-ratio: 16 / 9;
  }
  
  &__body {
    padding: $spacing-lg;
  }
  
  &__title {
    font-size: 1.125rem;
    font-weight: 600;
    margin: 0 0 $spacing-sm;
  }
  
  &--featured {
    border: 2px solid $color-primary;
  }
  
  &:hover {
    box-shadow: $shadow-md;
  }
}

This BEM-style nesting (&__element, &--modifier) generates classes like .card__image, .card__body, .card--featured.

Mixins

// _sass/_mixins.scss

@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

@mixin truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@mixin responsive($breakpoint) {
  @if $breakpoint == mobile {
    @media (max-width: 640px) { @content; }
  } @else if $breakpoint == tablet {
    @media (max-width: 1024px) { @content; }
  } @else if $breakpoint == desktop {
    @media (min-width: 1025px) { @content; }
  }
}

// Usage
.nav {
  @include flex-center;
  
  @include responsive(mobile) {
    flex-direction: column;
  }
}

.card__title {
  @include truncate;
}

Functions

// Convert px to rem
@function rem($px) {
  @return ($px / 16) * 1rem;
}

.heading {
  font-size: rem(24);  // β†’ 1.5rem
}

// Darken a colour by percentage
.btn:hover {
  background: darken($color-primary, 8%);
}

// Generate a colour with opacity
.overlay {
  background: rgba($color-text, 0.5);
}

Extends / placeholders

%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;
}

.sr-only {
  @extend %visually-hidden;
}

.skip-link:focus {
  @extend %visually-hidden;
  clip: auto;
  width: auto;
  height: auto;
}

Dark mode with Sass

// _sass/_variables.scss

// Light mode defaults (also used as CSS custom properties)
:root {
  --color-bg:     #{$color-bg};
  --color-text:   #{$color-text};
  --color-border: #{$color-border};
  --color-bg-alt: #{$color-bg-alt};
}

// Dark mode overrides
:root[data-theme="dark"],
.dark {
  --color-bg:     #0f172a;
  --color-text:   #f1f5f9;
  --color-border: #1e293b;
  --color-bg-alt: #1e293b;
}

// Use CSS custom properties throughout
body {
  background: var(--color-bg);
  color: var(--color-text);
}

.card {
  border-color: var(--color-border);
  background: var(--color-bg-alt);
}

This approach β€” setting CSS custom properties from Sass variables β€” gives you the best of both worlds: Sass for authoring, CSS variables for runtime dark mode toggling with JavaScript.

Sass compilation settings in _config.yml

# _config.yml
sass:
  sass_dir: _sass          # where partials live (default: _sass)
  style: compressed        # compressed | expanded | nested | compact
  load_paths:
    - _sass
    - node_modules         # if importing npm packages

style options:

  • compressed β€” removes all whitespace, one-line output. Use for production.
  • expanded β€” each rule and property on its own line. Default in development.
  • nested β€” rules nested to reflect the SCSS structure.
  • compact β€” one rule per line.

Most setups use compressed in production builds:

sass:
  style: compressed

Importing npm Sass packages

If you install Sass libraries via npm, add the path to load_paths:

npm install sass-mq normalize.css
sass:
  load_paths:
    - _sass
    - node_modules
// assets/css/main.scss
---
---
@import "normalize.css/normalize";
@import "sass-mq/mq";
@import "variables";
@import "base";

Organising a production-ready _sass/ directory

_sass/
β”œβ”€β”€ _variables.scss      ← design tokens
β”œβ”€β”€ _mixins.scss         ← reusable mixins
β”œβ”€β”€ _functions.scss      ← Sass functions
β”œβ”€β”€ _reset.scss          ← CSS reset/normalise
β”œβ”€β”€ _base.scss           ← global styles (body, a, h1-h6, img)
β”œβ”€β”€ _typography.scss     ← prose/content typography
β”‚
β”œβ”€β”€ layout/
β”‚   β”œβ”€β”€ _container.scss
β”‚   β”œβ”€β”€ _grid.scss
β”‚   └── _sections.scss
β”‚
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ _nav.scss
β”‚   β”œβ”€β”€ _footer.scss
β”‚   β”œβ”€β”€ _cards.scss
β”‚   β”œβ”€β”€ _badges.scss
β”‚   β”œβ”€β”€ _buttons.scss
β”‚   β”œβ”€β”€ _forms.scss
β”‚   └── _modals.scss
β”‚
β”œβ”€β”€ pages/
β”‚   β”œβ”€β”€ _home.scss
β”‚   β”œβ”€β”€ _blog.scss
β”‚   β”œβ”€β”€ _theme-detail.scss
β”‚   └── _authors.scss
β”‚
└── utilities/
    β”œβ”€β”€ _helpers.scss    ← .sr-only, .clearfix, etc.
    └── _dark-mode.scss
/* assets/css/main.scss */
---
---

// Tokens and tools
@import "variables";
@import "mixins";
@import "functions";

// Base
@import "reset";
@import "base";
@import "typography";

// Layout
@import "layout/container";
@import "layout/grid";
@import "layout/sections";

// Components
@import "components/nav";
@import "components/footer";
@import "components/cards";
@import "components/badges";
@import "components/buttons";
@import "components/forms";

// Pages
@import "pages/home";
@import "pages/blog";
@import "pages/theme-detail";
@import "pages/authors";

// Utilities
@import "utilities/helpers";
@import "utilities/dark-mode";

Common mistakes

Missing front matter on the entry point: Without the --- block, Jekyll copies main.scss as a plain text file instead of compiling it. Always include the empty front matter.

Importing from the wrong path: Partials in _sass/ are imported without the leading _ or the directory path. If your partial is _sass/components/_nav.scss, import it as @import "components/nav".

Using @use instead of @import: Jekyll’s built-in Sass processor uses libsass which supports @import but has limited support for the newer @use syntax. Stick with @import for Jekyll’s native Sass processing. If you need @use, switch to a Node.js PostCSS pipeline.

Not compressing in production: Add sass: style: compressed to _config.yml or use a production build command to minimise CSS output.

Built-in Sass support is one of Jekyll’s most useful features. No Node.js, no build pipeline, no configuration β€” just write SCSS and Jekyll compiles it. For most Jekyll sites, the built-in processor is all you need.

Share LinkedIn