@taraskucherenko/scss-kit
v0.8.99
Published
Foundational SCSS toolkit for scalable UI development
Downloads
213
Maintainers
Readme
@taraskucherenko/scss-kit
🇺🇦 Українська | 🇬🇧 English
Українська
Про проєкт
scss-kit — персональний SCSS-фреймворк для швидкого старту нових проєктів. Містить:
- CSS reset
- SCSS-змінні (кольори, шрифти, розміри сітки) — усі
!default, перевизначаються - Функції (
oklch,oklab,px-to-rem,img-url, …) - Міксини (медіа-квері, стани, псевдо, fonts, тема, сітка, типографіка, …)
- Атоми — статичні утиліти-класи +
%placeholder-селектори
Встановлення
yarn add @taraskucherenko/scss-kit
# або
npm install @taraskucherenko/scss-kitВимоги: Sass >= 1.97.2 (peerDependency)
Підключення
Рекомендований сучасний підхід — через pkg: URL. Один і той самий рядок працює з будь-якої глибини файлу:
@use 'pkg:@taraskucherenko/scss-kit' as *;Щоб це запрацювало, у твоєму збірнику треба ввімкнути Node Package Importer.
Налаштування під різні збірники
Sass CLI:
sass --pkg-importer=node main.scss main.cssVite:
// vite.config.js
import { defineConfig } from 'vite';
import { NodePackageImporter } from 'sass';
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
importers: [new NodePackageImporter()]
}
}
}
});Gulp + gulp-sass:
// gulpfile.js
const gulp = require('gulp');
const sass = require('sass');
const { NodePackageImporter } = sass;
const gulpSass = require('gulp-sass')(sass);
gulp.task('styles', () =>
gulp.src('src/**/*.scss')
.pipe(gulpSass({ importers: [new NodePackageImporter()] }).on('error', gulpSass.logError))
.pipe(gulp.dest('dist/css'))
);Webpack + sass-loader (≥ 13.3):
// webpack.config.js
const { NodePackageImporter } = require('sass');
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
sassOptions: {
importers: [new NodePackageImporter()]
}
}
}
]
}
]
}
};Fallback (без pkg: importer)
Якщо ввімкнути pkg: importer неможливо (старіший Sass або обмеження тулінгу) — можна імпортувати через відносний шлях. Кількість ../ залежить від глибини файлу:
@use '../../../node_modules/@taraskucherenko/scss-kit/src/main' as *;Окремі шари (для .module.scss)
exports мапить ./src/*, тож будь-який шар можна підключити окремо, не тягнучи корінь. Саме це потрібно в CSS-модулях: корінь емітить reset і атоми, а в модулі цей вивід заінлайниться в кожен файл, який його підключив.
// Button.module.scss
@use 'pkg:@taraskucherenko/scss-kit/src/variables' as *;
@use 'pkg:@taraskucherenko/scss-kit/src/functions' as *;
@use 'pkg:@taraskucherenko/scss-kit/src/mixins' as *;
.button {
padding: px-to-rem(16);
@include media-t-768 { padding: px-to-rem(12); }
}variables і functions не дають жодного рядка CSS — це чисті змінні й функції. mixins дає ~400 байт: _mix-theme.scss містить п'ять top-level @keyframes (rotating, indicator-width, loading, bg-skeleton, icon-translate), і вони емітяться при кожному підключенні шару. У CSS-модулях це продублюється на кожен модуль — якщо заважає, лишай у модулі тільки variables + functions, а mixins підключай у глобальному файлі.
Reset і атоми — один раз глобально, з кореня.
Перевизначення дефолтних змінних
Усі публічні змінні позначені !default. Перевизначай їх через with (...) при імпорті:
@use 'pkg:@taraskucherenko/scss-kit' as * with (
// обов'язково для img-url() — без цього функція кине @error
$img-path: '/assets/img',
// обов'язково для @include font() — без цього міксин кине @error
$font-path: '/assets/fonts',
// сітка
$page-width-max: 1280,
$page-width-min: 320,
$col-gutter: 24,
$cols: 8,
// кольори
$col-black: #111111,
$col-white: #FAFAFA,
// шрифти (списки — щоб мати fallback)
$font-family-default: ('Inter', system-ui, sans-serif),
$font-plain: ('Inter', system-ui, sans-serif),
$font-titles: ('Playfair Display', Georgia, serif),
// тривалість анімації для атома .anim
$anim-duration: 0.3s,
// z-index шкала (доступно: $z-ind-background, $z-ind-backward, $z-ind-0..3,
// $z-ind-select, $z-ind-tooltip, $z-ind-high,
// $z-ind-overlay, $z-ind-modal)
$z-ind-modal: 9999,
// емітити атоми ще й класами (.flex) - дефолт лише %плейсхолдери
$emit-atom-classes: true
);Емісія атомних класів
Кожен атом існує як %placeholder завжди - компоненти композиціюють їх через @extend.
Класами ж за замовчуванням емітиться лише markup-ядро ($core-atoms, ~30 штук:
flex-сімейство, gap-*, w-full, text-*, fw-medium/semi-bold/bold, relative,
anim, sr-only) - емпіричний список того, що реально пишуть у розмітку. Керується
двома прапорцями:
// дефолт: ядро класами + всі placeholder'и
@use 'pkg:@taraskucherenko/scss-kit' as *;
// всі атоми класами (вивід як у <= 0.8.79)
@use 'pkg:@taraskucherenko/scss-kit' as * with ($emit-atom-classes: true);
// взагалі без класів - лише placeholder'и
@use 'pkg:@taraskucherenko/scss-kit' as * with ($emit-core-classes: false);
// своє ядро (повна заміна списку; кожне ім'я мусить існувати - одрук валить збірку)
@use 'pkg:@taraskucherenko/scss-kit' as * with ($core-atoms: ('flex', 'gap-1', 'line-h-1'));Чому так: заміри по реальних проєктах показали, що розмітка використовує ~10 % атомних
класів - і скрізь ті самі, - а решта була мертвою вагою в кожному бандлі (у типовому
проєкті ~7 KB мініфікованого CSS). ⚠️ Вимикання класів не ламає збірку, якщо
розмітка їх ще використовує - вони просто тихо зникають зі стилів. Перед вимкненням
перевір розмітку: grep -rE 'class[^=]*="[^"]*(flex|gap-|w-full)' src/.
Семантичні токени (опційний шар)
Окремий вхід src/tokens. Свідомо не форвардиться з main.scss: він емітить CSS (:root-блоки), тож потрапивши в корінь — змінив би вивід усім, хто його вже підключив. Атоми лишаються як були; шар стоїть поряд, а не над ними.
@use 'pkg:@taraskucherenko/scss-kit/src/tokens' as tok with (
$fg: (base: #1A1C1E, accent: #0B5FBF),
$bg: (page: #FFFFFF, surface: #FFFFFF),
$bd: (base: #D6D9DE),
$shad: (dropdown: 0 4px 16px rgba(0, 0, 0, 0.12)),
// ungrouped — емітиться як --state-mix, без префікса групи
$extra: ('state-mix': #000000),
// темна тема; лишиш порожньою — блок не емітиться взагалі
$fg-dark: (base: #F2F3F5, accent: #7FB4F5),
$bg-dark: (page: #1A1C1E, surface: #24272B),
$bd-dark: (base: #3F444B),
$shad-dark: (dropdown: 0 4px 16px rgba(0, 0, 0, 0.55)),
$extra-dark: ('state-mix': #FFFFFF),
);
.card {
@include tok.bg(surface);
@include tok.bd(base);
@include tok.fg(base);
@include tok.shad(dropdown);
}Що дає шар, чого не дає ручний var(--…):
- помилка в імені валить білд із переліком доступних токенів, замість мертвого
var(--fg-acent), що тихо тече в прод; - токен, забутий у темній мапі, теж валить білд — інакше він мовчки лишив би світле значення, і це випливло б місяцями пізніше як білий текст на білому;
- дефолтні значення звірені по WCAG AA, тож проєкт без своєї палітри вже читний.
Додатково модуль дає tint($color, $percent) (бо rgba() не приймає var()), focus-ring($color, $size, $imp) і overlay-fill($position).
Дефолти емітяться на :root; змінюється через $root. Темний блок пишеться двічі — під [data-theme='dark'] і під prefers-color-scheme, щоб явний вибір користувача вигравав в обидва боки. Атрибут задається через $theme-attr (null — лише системна тема). $emit: false віддає самі аксесори, без :root-блоків.
Що далі
- Згенерувати кольорову палітру:
:root { @include generate-colors($yourMap); } - Підключити кастомні шрифти:
@include font('Inter-Regular', 400, 'Inter')(шлях береться з$font-path); для italic —@include font('Inter-Italic', 400, 'Inter', $style: italic) - Використовувати атоми (
.bg-white,.round-8,.gap-1) або плейсхолдери (@extend %fg-current) - Ставити медіа-квері:
@include media-t-768 { ... }
English
About
scss-kit is a personal SCSS framework that bootstraps new projects with:
- CSS reset
- SCSS variables (colors, fonts, grid sizes) — all
!default, overridable - Functions (
oklch,oklab,px-to-rem,img-url, …) - Mixins (media queries, states, pseudo, fonts, theme, grid, typography, …)
- Atoms — static utility classes +
%placeholderselectors
Installation
yarn add @taraskucherenko/scss-kit
# or
npm install @taraskucherenko/scss-kitRequires: Sass >= 1.97.2 (peerDependency)
Usage
The recommended modern approach is via pkg: URL. The same line works from any file depth:
@use 'pkg:@taraskucherenko/scss-kit' as *;For this to work, your build tool needs the Node Package Importer enabled.
Build tool configuration
Sass CLI:
sass --pkg-importer=node main.scss main.cssVite:
// vite.config.js
import { defineConfig } from 'vite';
import { NodePackageImporter } from 'sass';
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
importers: [new NodePackageImporter()]
}
}
}
});Gulp + gulp-sass:
// gulpfile.js
const gulp = require('gulp');
const sass = require('sass');
const { NodePackageImporter } = sass;
const gulpSass = require('gulp-sass')(sass);
gulp.task('styles', () =>
gulp.src('src/**/*.scss')
.pipe(gulpSass({ importers: [new NodePackageImporter()] }).on('error', gulpSass.logError))
.pipe(gulp.dest('dist/css'))
);Webpack + sass-loader (≥ 13.3):
// webpack.config.js
const { NodePackageImporter } = require('sass');
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
sassOptions: {
importers: [new NodePackageImporter()]
}
}
}
]
}
]
}
};Fallback (without pkg: importer)
If you cannot enable the pkg: importer (older Sass or tooling limitations), you can import via a relative path. The number of ../ depends on file depth:
@use '../../../node_modules/@taraskucherenko/scss-kit/src/main' as *;Individual layers (for .module.scss)
exports maps ./src/*, so any layer can be pulled in on its own without the root. This is what CSS modules need: the root emits the reset and the atoms, and inside a module that output gets inlined into every file importing it.
// Button.module.scss
@use 'pkg:@taraskucherenko/scss-kit/src/variables' as *;
@use 'pkg:@taraskucherenko/scss-kit/src/functions' as *;
@use 'pkg:@taraskucherenko/scss-kit/src/mixins' as *;
.button {
padding: px-to-rem(16);
@include media-t-768 { padding: px-to-rem(12); }
}variables and functions emit no CSS at all — they are plain variables and functions. mixins emits ~400 bytes: _mix-theme.scss holds five top-level @keyframes (rotating, indicator-width, loading, bg-skeleton, icon-translate), and they land in the output every time the layer is used. In CSS modules that duplicates per module — if it matters, keep only variables + functions in the module and import mixins from a global file.
The reset and the atoms stay where they belong: once, globally, from the root.
Overriding defaults
All public variables are marked !default. Override them via with (...) at import time:
@use 'pkg:@taraskucherenko/scss-kit' as * with (
// required for img-url() — otherwise it throws @error
$img-path: '/assets/img',
// required for @include font() — otherwise it throws @error
$font-path: '/assets/fonts',
// grid
$page-width-max: 1280,
$page-width-min: 320,
$col-gutter: 24,
$cols: 8,
// colors
$col-black: #111111,
$col-white: #FAFAFA,
// fonts (lists — keep fallbacks)
$font-family-default: ('Inter', system-ui, sans-serif),
$font-plain: ('Inter', system-ui, sans-serif),
$font-titles: ('Playfair Display', Georgia, serif),
// animation duration for the .anim atom
$anim-duration: 0.3s,
// z-index scale (any of: $z-ind-background, $z-ind-backward, $z-ind-0..3,
// $z-ind-select, $z-ind-tooltip, $z-ind-high,
// $z-ind-overlay, $z-ind-modal)
$z-ind-modal: 9999,
// also emit atoms as classes (.flex) - the default is %placeholders only
$emit-atom-classes: true
);Atom class emission
Every atom always exists as a %placeholder - components compose them with @extend.
As classes, the default emits only the markup core ($core-atoms, ~30 names: the
flex family, gap-*, w-full, text-*, fw-medium/semi-bold/bold, relative,
anim, sr-only) - the empirical list of what actually gets written into markup. Two
flags control it:
// default: the core as classes + all placeholders
@use 'pkg:@taraskucherenko/scss-kit' as *;
// every atom as a class (the <= 0.8.79 output)
@use 'pkg:@taraskucherenko/scss-kit' as * with ($emit-atom-classes: true);
// no classes at all - placeholders only
@use 'pkg:@taraskucherenko/scss-kit' as * with ($emit-core-classes: false);
// your own core (replaces the list; every name must exist - a typo fails the build)
@use 'pkg:@taraskucherenko/scss-kit' as * with ($core-atoms: ('flex', 'gap-1', 'line-h-1'));Why: measured across real consumers, markup uses ~10 % of the atom classes - the same
few everywhere - while the rest was dead weight in every bundle (~7 KB of minified CSS
in a typical project). ⚠️ Turning classes off does not fail the build when the
markup still uses them - they just silently disappear from the styles. Check the markup
first: grep -rE 'class[^=]*="[^"]*(flex|gap-|w-full)' src/.
Semantic tokens (opt-in layer)
A separate entry point, src/tokens. Deliberately not forwarded from main.scss: it emits CSS (the :root blocks), so pulling it into the root would change the output of every project already using the kit. The atoms keep working as before — this layer sits beside them, not over them.
@use 'pkg:@taraskucherenko/scss-kit/src/tokens' as tok with (
$fg: (base: #1A1C1E, accent: #0B5FBF),
$bg: (page: #FFFFFF, surface: #FFFFFF),
$bd: (base: #D6D9DE),
$shad: (dropdown: 0 4px 16px rgba(0, 0, 0, 0.12)),
// ungrouped — emitted as --state-mix, with no group prefix
$extra: ('state-mix': #000000),
// dark theme; leave the maps empty and no dark block is emitted at all
$fg-dark: (base: #F2F3F5, accent: #7FB4F5),
$bg-dark: (page: #1A1C1E, surface: #24272B),
$bd-dark: (base: #3F444B),
$shad-dark: (dropdown: 0 4px 16px rgba(0, 0, 0, 0.55)),
$extra-dark: ('state-mix': #FFFFFF),
);
.card {
@include tok.bg(surface);
@include tok.bd(base);
@include tok.fg(base);
@include tok.shad(dropdown);
}What the layer buys over hand-written var(--…):
- a misspelled name fails the build, listing the tokens that do exist, instead of leaking a dead
var(--fg-acent)into production; - a token missing from the dark map fails the build too — otherwise it silently keeps its light value, and that surfaces months later as white text on white;
- the shipped defaults are contrast-checked against WCAG AA, so a project that has not picked a palette is still readable.
The module also carries tint($color, $percent) (because rgba() cannot take a var()), focus-ring($color, $size, $imp) and overlay-fill($position).
Tokens land on :root by default — change it with $root. The dark block is written twice, once for [data-theme='dark'] and once for prefers-color-scheme, so an explicit choice wins in both directions; the attribute is set via $theme-attr (null for OS preference only). $emit: false gives you the accessors without the :root blocks.
What's next
- Generate a color palette:
:root { @include generate-colors($yourMap); } - Register custom fonts:
@include font('Inter-Regular', 400, 'Inter')(path comes from$font-path); for italic —@include font('Inter-Italic', 400, 'Inter', $style: italic) - Use atoms (
.bg-white,.round-8,.gap-1) or placeholders (@extend %fg-current) - Apply media queries:
@include media-t-768 { ... }
Author
Taras Kucherenko GitLab: gitlab.com/teekeydev/scss-kit
