Більшість розробників сприймають CSS як дескриптивну мову: «задай колір, задай відступ, задай шрифт». Але сучасний CSS — це мова обчислень. Він може обчислювати арифметичні вирази між різними одиницями, виконувати тригонометрію, округлювати числа, витягувати системні константи і навіть реалізовувати умовну логіку — без жодного JavaScript.
Функції CSS не просто зручна нотація: вони дозволяють виразити наміри, а не фіксовані значення. Замість «padding: 24px» ми пишемо «padding: clamp(1rem, 3vw, 2.5rem)» — і браузер сам обраховує правильне значення для кожного viewport.
calc() — глибокий divecalc() — перша й найбільш відома CSS-функція, з'явилась у 2012 році. Вона дозволяє змішувати різні одиниці в одному виразі.
.element {
/* ✅ Пробіли навколо операторів + і - обов'язкові */
width: calc(100% - 2rem);
/* ✅ Множення і ділення — без пробілів допустимо, але зі — краще */
font-size: calc(1rem * 1.5);
/* ✅ Вкладений calc (спрощується в сучасних браузерах) */
padding: calc(calc(100% / 3) - 1rem);
/* ❌ Дільник не може мати одиниці */
/* width: calc(100px / 2px); */
/* ✅ Дільник — лише число без одиниць */
width: calc(100px / 2);
}
calc() у контексті різних властивостей/* Класичні паттерни */
.sidebar-layout {
display: flex;
}
.sidebar {
width: 280px;
flex-shrink: 0;
}
.main-content {
width: calc(100% - 280px); /* для float-layouts */
}
/* Декоративне позиціонування */
.centered-badge {
left: calc(50% - 20px); /* центр мінус половина ширини */
}
/* Динамічний line-height */
.readable {
line-height: calc(1em + 0.5rem); /* базовий + відступ */
}
/* Кольори через hsl */
.dynamic-hue {
--base-hue: 240;
background: hsl(calc(var(--base-hue) + 30), 80%, 60%);
}
<div class="calc-showcase">
<div class="cs-layout">
<div class="cs-sidebar">Sidebar<br><small>280px</small></div>
<div class="cs-main">Main: calc(100% - 280px - 0.75rem)</div>
</div>
<div class="cs-grid">
<div class="cs-col">1/3: calc(100%/3 - 0.5rem)</div>
<div class="cs-col">1/3: calc(100%/3 - 0.5rem)</div>
<div class="cs-col">1/3: calc(100%/3 - 0.5rem)</div>
</div>
<div class="cs-badge-wrapper">
<span class="cs-badge">Новинка!</span>
<div class="cs-card-image">Зображення</div>
<p class="cs-card-text">Бейдж позиціонований через<br><code>left: calc(50% - 35px)</code></p>
</div>
</div>
.calc-showcase {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.85rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.cs-layout { display: flex; gap: 0.75rem; height: 70px; }
.cs-sidebar {
width: 120px; flex-shrink: 0;
background: #6366f1; color: white;
border-radius: 6px; padding: 0.5rem;
display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center;
}
.cs-sidebar small { opacity: 0.8; font-size: 0.7rem; }
.cs-main {
width: calc(100% - 120px - 0.75rem);
background: white; border: 1px solid #e2e8f0;
border-radius: 6px; padding: 0.5rem;
display: flex; align-items: center; font-size: 0.78rem;
}
.cs-grid { display: flex; gap: 0.5rem; }
.cs-col {
width: calc(100% / 3 - 0.33rem);
background: white; border: 1px solid #e2e8f0;
border-radius: 6px; padding: 0.5rem;
font-size: 0.75rem; text-align: center; flex-shrink: 0;
}
.cs-badge-wrapper {
position: relative;
background: white; border-radius: 8px;
border: 1px solid #e2e8f0; overflow: hidden;
}
.cs-card-image {
height: 60px; background: linear-gradient(135deg, #6366f1, #8b5cf6);
color: white; display: flex; align-items: center; justify-content: center;
font-weight: 700;
}
.cs-badge {
position: absolute;
top: 10px;
left: calc(50% - 35px);
background: #ec4899; color: white;
padding: 0.2rem 0.5rem; border-radius: 100px;
font-size: 0.72rem; font-weight: 700;
white-space: nowrap;
}
.cs-card-text { margin: 0; padding: 0.5rem; font-size: 0.78rem; text-align: center; }
.cs-card-text code { background: #f1f5f9; padding: 0.1em 0.3em; border-radius: 3px; color: #6366f1; }
min(), max(), clamp() — умовна геометріяДетальний розбір цих функцій є у статті Розміри у CSS. Тут розглянемо просунуті паттерни.
Одне з найважливіших застосувань clamp() — fluid spacing: відступи, що природно масштабуються між мобільною та десктопною версіями без медіа-запитів.
<div class="fluid-spacing-demo">
<section class="fsd-section">
<h2 class="fsd-h2">Заголовок</h2>
<p class="fsd-text">Усі відступи fluid: масштабуються між мінімумом та максимумом залежно від viewport. Зміните ширину вікна та простежте зміни padding.</p>
<div class="fsd-cards">
<div class="fsd-card">Картка A</div>
<div class="fsd-card">Картка B</div>
<div class="fsd-card">Картка C</div>
</div>
</section>
</div>
/* Fluid Spacing Scale */
:root {
--space-xs: clamp(0.25rem, 0.5vw, 0.5rem);
--space-sm: clamp(0.5rem, 1vw, 1rem);
--space-md: clamp(1rem, 2vw, 1.5rem);
--space-lg: clamp(1.5rem, 4vw, 3rem);
--space-xl: clamp(2rem, 6vw, 5rem);
--space-2xl: clamp(3rem, 8vw, 8rem);
/* Fluid Typography Scale */
--text-sm: clamp(0.8rem, 1vw + 0.5rem, 0.95rem);
--text-base: clamp(0.9rem, 1.2vw + 0.5rem, 1.05rem);
--text-lg: clamp(1rem, 1.5vw + 0.5rem, 1.25rem);
--text-xl: clamp(1.25rem, 2vw + 0.5rem, 1.75rem);
--text-2xl: clamp(1.5rem, 3vw + 0.5rem, 2.5rem);
}
.fluid-spacing-demo {
background: #f1f5f9;
font-family: system-ui, sans-serif;
color: #1e293b;
}
.fsd-section {
padding: var(--space-lg) var(--space-md);
display: flex;
flex-direction: column;
gap: var(--space-md);
}
.fsd-h2 {
margin: 0;
font-size: var(--text-2xl);
color: #6366f1;
}
.fsd-text {
margin: 0;
font-size: var(--text-base);
line-height: 1.6;
max-width: 60ch;
}
.fsd-cards {
display: flex;
gap: var(--space-sm);
flex-wrap: wrap;
}
.fsd-card {
flex: 1;
min-width: 80px;
background: white;
border-radius: 8px;
padding: var(--space-sm) var(--space-md);
border: 1px solid #e2e8f0;
text-align: center;
font-weight: 600;
font-size: var(--text-sm);
}
CSS підтримує повний набір тригонометрії: sin(), cos(), tan(), asin(), acos(), atan(), atan2(). А також математичні константи: pi, e, infinity, NaN.
Підтримка: Chrome 111+, Firefox 108+, Safari 15.4+
Класичний приклад — іконки по колу без JavaScript:
<div class="trig-demo">
<p class="trig-note">8 елементів по колу через sin() та cos()</p>
<div class="circle-layout">
<div class="cl-center">CSS</div>
<div class="cl-item" style="--i:0">🎨</div>
<div class="cl-item" style="--i:1">📐</div>
<div class="cl-item" style="--i:2">⚡</div>
<div class="cl-item" style="--i:3">🌊</div>
<div class="cl-item" style="--i:4">🎯</div>
<div class="cl-item" style="--i:5">🔮</div>
<div class="cl-item" style="--i:6">💫</div>
<div class="cl-item" style="--i:7">🚀</div>
</div>
<div class="wave-demo">
<div class="wave-items">
<div class="wave-dot" style="--j:0"></div>
<div class="wave-dot" style="--j:1"></div>
<div class="wave-dot" style="--j:2"></div>
<div class="wave-dot" style="--j:3"></div>
<div class="wave-dot" style="--j:4"></div>
<div class="wave-dot" style="--j:5"></div>
<div class="wave-dot" style="--j:6"></div>
<div class="wave-dot" style="--j:7"></div>
<div class="wave-dot" style="--j:8"></div>
<div class="wave-dot" style="--j:9"></div>
<div class="wave-dot" style="--j:10"></div>
<div class="wave-dot" style="--j:11"></div>
</div>
<p class="trig-note">Хвиля через translateY(calc(sin(var(--angle)) * 20px))</p>
</div>
</div>
.trig-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.875rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 1rem;
}
.trig-note { margin: 0; font-size: 0.78rem; color: #64748b; font-style: italic; }
.circle-layout {
position: relative;
width: 200px;
height: 200px;
margin: 0 auto;
}
.cl-center {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 50px; height: 50px;
background: #6366f1;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 0.85rem;
}
.cl-item {
--count: 8;
--radius: 80px;
--angle: calc(var(--i) * 360deg / var(--count));
position: absolute;
top: 50%; left: 50%;
width: 36px; height: 36px;
border-radius: 50%;
background: white;
border: 2px solid #e2e8f0;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.1rem;
/* sin/cos для позиціонування по колу */
transform: translate(
calc(-50% + cos(var(--angle)) * var(--radius)),
calc(-50% + sin(var(--angle)) * var(--radius))
);
}
.wave-items {
display: flex;
align-items: center;
gap: 6px;
height: 60px;
justify-content: center;
}
.wave-dot {
--angle: calc(var(--j) * 30deg);
width: 12px; height: 12px;
border-radius: 50%;
background: #6366f1;
transform: translateY(calc(sin(var(--angle)) * -18px));
animation: wave-anim 2s ease-in-out infinite;
animation-delay: calc(var(--j) * 0.1s);
}
@keyframes wave-anim {
0%, 100% { transform: translateY(calc(sin(var(--angle)) * -18px)); }
50% { transform: translateY(calc(sin(var(--angle)) * 18px)); }
}
round(), mod(), rem()CSS Level 4 додав математичні функції для роботи з сіткою та числовими паттернами:
/* round(strategy, value, interval) */
.grid-aligned {
/* Округлити до найближчих 8px (базова одиниця сітки) */
margin-top: round(nearest, var(--spacing), 8px);
/* Завжди вгору — наступне кратне 16px */
padding: round(up, 17px, 16px); /* → 32px */
/* Завжди вниз */
font-size: round(down, 15px, 4px); /* → 12px */
}
/* mod(value, modulus) — залишок від ділення */
.alternating {
/* 0, 1, 2, 3, 0, 1, 2, 3... */
background-hue: calc(mod(var(--index), 4) * 90);
}
/* rem(value, divisor) — залишок зі знаком */
.signed-remainder {
offset: rem(-7px, 4px); /* → -3px */
}
<div class="rounding-demo">
<p class="rd-title">round() для вирівнювання до сітки:</p>
<div class="grid-8">
<div class="g8-item" style="--raw: 13px">raw: 13px → round: 8px</div>
<div class="g8-item" style="--raw: 17px">raw: 17px → round: 16px</div>
<div class="g8-item" style="--raw: 22px">raw: 22px → round: 24px</div>
<div class="g8-item" style="--raw: 30px">raw: 30px → round: 32px</div>
</div>
<p class="rd-title">mod() для чергування кольорів:</p>
<div class="mod-colors">
<div class="mc-item" style="--i:0">0</div>
<div class="mc-item" style="--i:1">1</div>
<div class="mc-item" style="--i:2">2</div>
<div class="mc-item" style="--i:3">3</div>
<div class="mc-item" style="--i:4">4</div>
<div class="mc-item" style="--i:5">5</div>
<div class="mc-item" style="--i:6">6</div>
<div class="mc-item" style="--i:7">7</div>
</div>
</div>
.rounding-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.82rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.rd-title { margin: 0; font-weight: 700; color: #64748b; }
.grid-8 { display: flex; flex-direction: column; gap: 2px; }
.g8-item {
background: white;
border: 1px solid #e2e8f0;
border-radius: 4px;
padding: 0.3rem 0.5rem;
font-size: 0.75rem;
border-left: 3px solid #6366f1;
padding-left: round(nearest, var(--raw), 8px);
}
.mod-colors { display: flex; gap: 4px; flex-wrap: wrap; }
.mc-item {
width: 36px; height: 36px;
border-radius: 6px;
display: flex; align-items: center; justify-content: center;
font-weight: 700; color: white;
font-size: 0.85rem;
background: hsl(calc(mod(var(--i), 4) * 90), 70%, 55%);
}
env() — системні змінні середовищаenv() дає доступ до системних значень, що недоступні через звичайний CSS.
<div class="env-demo">
<div class="safe-area-demo">
<div class="sa-top">
<code>padding-top: env(safe-area-inset-top, 20px)</code>
</div>
<div class="sa-content">
Основний контент — поза зоною notch та home indicator
</div>
<div class="sa-bottom">
<code>padding-bottom: env(safe-area-inset-bottom, 20px)</code>
</div>
</div>
<div class="env-info">
<p><strong>Доступні env() змінні:</strong></p>
<ul>
<li><code>safe-area-inset-top</code> — відступ від notch зверху</li>
<li><code>safe-area-inset-right</code> — правий відступ</li>
<li><code>safe-area-inset-bottom</code> — home indicator</li>
<li><code>safe-area-inset-left</code> — лівий відступ</li>
<li><code>titlebar-area-height</code> — PWA titlebar</li>
</ul>
</div>
</div>
.env-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.85rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.safe-area-demo {
background: #1e293b;
border-radius: 20px;
overflow: hidden;
display: flex;
flex-direction: column;
max-width: 260px;
margin: 0 auto;
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
}
.sa-top, .sa-bottom {
background: #0f172a;
padding: 0.75rem;
text-align: center;
font-size: 0.65rem;
color: #94a3b8;
font-family: monospace;
}
.sa-content {
background: linear-gradient(135deg, #6366f1, #8b5cf6);
color: white;
padding: 1.5rem 1rem;
text-align: center;
font-size: 0.82rem;
line-height: 1.4;
flex: 1;
}
.env-info { background: white; border: 1px solid #e2e8f0; border-radius: 8px; padding: 0.75rem; }
.env-info p { margin: 0 0 0.4rem; }
.env-info ul { margin: 0; padding-left: 1.25rem; display: flex; flex-direction: column; gap: 0.2rem; }
.env-info li { font-size: 0.78rem; }
.env-info code { background: #f1f5f9; padding: 0.1em 0.3em; border-radius: 3px; color: #6366f1; font-size: 0.85em; }
/* Правильне використання для мобільних додатків */
.app-shell {
padding-top: env(safe-area-inset-top);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
}
/* З fallback для десктопу */
.bottom-nav {
padding-bottom: max(1rem, env(safe-area-inset-bottom));
}
Layout Primitives — це невеликі, повторно використовувані CSS-паттерни, з яких будуються складні layouts. Концепцію популяризував Heydon Pickering у «Every Layout».
<div class="stack-demo">
<p class="lp-label">The Stack: рівні відступи між елементами через Lobotomized Owl</p>
<div class="stack">
<div class="stack-item">Елемент 1</div>
<div class="stack-item">Елемент 2</div>
<div class="stack-item">Елемент 3 (без відступу знизу)</div>
</div>
<p class="lp-code"><code>* + * { margin-top: 1rem }</code> — відступ лише між елементами, не перед першим</p>
</div>
.stack-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.85rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.lp-label { margin: 0; color: #64748b; }
.lp-code { margin: 0; font-size: 0.75rem; color: #94a3b8; }
.lp-code code { background: #f1f5f9; padding: 0.1em 0.3em; border-radius: 3px; color: #6366f1; }
.stack {
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 0.75rem;
}
/* Lobotomized Owl selector */
.stack > * + * { margin-top: 0.75rem; }
.stack-item {
background: #ede9fe;
border-radius: 6px;
padding: 0.5rem 0.75rem;
color: #5b21b6;
font-weight: 600;
}
<div class="sidebar-prim-demo">
<p class="lp-label">The Sidebar: коли sidebar не вміщується за шириною — переходить під контент</p>
<div class="sidebar-prim">
<aside class="sp-aside">
Sidebar<br>
<small>min-width: 140px<br>flex-basis: 200px</small>
</aside>
<main class="sp-main">
Main content<br>
<small>flex: 1 — займає весь залишок.<br>Якщо sidebar не вміщується — переходить на новий рядок автоматично.</small>
</main>
</div>
</div>
.sidebar-prim-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.85rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.sidebar-prim {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
.sp-aside {
flex-basis: 160px;
flex-grow: 1;
background: #6366f1;
color: white;
border-radius: 8px;
padding: 0.75rem;
line-height: 1.4;
}
.sp-aside small { opacity: 0.8; font-size: 0.72rem; font-style: italic; }
.sp-main {
flex: 999 1 0; /* flex-grow: 999 — завжди займає весь залишок якщо вміщується */
min-width: min(300px, 100%);
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 0.75rem;
line-height: 1.4;
}
.sp-main small { color: #64748b; font-style: italic; font-size: 0.75rem; }
RAM (Repeat, Auto, Minmax) — найпопулярніший grid-патерн для адаптивних галерей без медіа-запитів:
<div class="ram-demo">
<p class="lp-label">The RAM: grid без media queries — колонки самі визначають кількість</p>
<div class="ram-grid">
<div class="ram-item">🎨 Design</div>
<div class="ram-item">⚡ Speed</div>
<div class="ram-item">🧩 Modular</div>
<div class="ram-item">📱 Responsive</div>
<div class="ram-item">🔒 Secure</div>
<div class="ram-item">♿ Accessible</div>
<div class="ram-item">🌙 Dark Mode</div>
<div class="ram-item">🚀 Performance</div>
</div>
<code class="ram-code">grid-template-columns: repeat(auto-fill, minmax(min(120px, 100%), 1fr))</code>
</div>
.ram-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.85rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.ram-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(120px, 100%), 1fr));
gap: 0.5rem;
}
.ram-item {
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 0.75rem 0.5rem;
text-align: center;
font-size: 0.8rem;
font-weight: 600;
transition: all 0.15s;
}
.ram-item:hover {
border-color: #6366f1;
background: #ede9fe;
color: #4f46e5;
}
.ram-code {
font-family: monospace;
font-size: 0.72rem;
color: #6366f1;
background: white;
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 0.4rem 0.6rem;
display: block;
overflow-x: auto;
white-space: nowrap;
}
<div class="reel-demo">
<p class="lp-label">The Reel: горизонтальний скрол без overflow tricks</p>
<div class="reel">
<div class="reel-item r1">🎬 Фільм 1</div>
<div class="reel-item r2">📺 Серіал 2</div>
<div class="reel-item r3">🎭 Вистава 3</div>
<div class="reel-item r4">🎵 Концерт 4</div>
<div class="reel-item r5">⚽ Спорт 5</div>
<div class="reel-item r6">🎮 Ігри 6</div>
</div>
</div>
.reel-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.85rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.reel {
display: flex;
gap: 0.75rem;
overflow-x: auto;
overflow-y: hidden;
scroll-snap-type: x mandatory;
overscroll-behavior-x: contain;
-webkit-overflow-scrolling: touch;
padding-bottom: 0.5rem; /* місце для scrollbar */
scrollbar-width: thin;
scrollbar-color: #c4b5fd #f1f5f9;
}
.reel-item {
flex: 0 0 140px;
height: 100px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: white;
scroll-snap-align: start;
font-size: 0.85rem;
}
.r1 { background: linear-gradient(135deg, #6366f1, #8b5cf6); }
.r2 { background: linear-gradient(135deg, #10b981, #059669); }
.r3 { background: linear-gradient(135deg, #ec4899, #f43f5e); }
.r4 { background: linear-gradient(135deg, #f59e0b, #d97706); }
.r5 { background: linear-gradient(135deg, #3b82f6, #2563eb); }
.r6 { background: linear-gradient(135deg, #8b5cf6, #7c3aed); }
| Функція | Категорія | Використання |
|---|---|---|
calc() | Арифметика | Змішування одиниць |
min(a, b) | Порівняння | Не більше за значення |
max(a, b) | Порівняння | Не менше за значення |
clamp(min, val, max) | Fluid | Обмежений діапазон |
round(s, v, i) | Округлення | Вирівнювання до сітки |
mod(v, m) | Математика | Залишок від ділення |
sin(), cos(), tan() | Тригонометрія | Позиціонування по колу |
atan2(y, x) | Тригонометрія | Кут між точками |
abs() | Математика | Абсолютне значення |
env() | Система | safe-area-inset, PWA |
var() | Змінні | Custom Properties |
color-mix() | Колір | Змішування кольорів |
light-dark() | Тема | Авто-вибір для теми |
Реалізуйте картку, де:
min(100%, 400px)clamp(1rem, 3vw, 2rem)clamp(1.1rem, 2vw + 0.5rem, 1.5rem)clamp(0.85rem, 1.5vw, 1rem)Реалізуйте годинникові стрілки через CSS:
rotate() для анімаціїcos() та sin() для кінцевих точок стрілокanimation: clock-hand linear infinite з animation-durationПобудуйте повну систему дизайн-токенів:
clamp()clamp()color-mix() (10 відтінків від одного brand color)@property для анімованих color tokensДоступність у CSS (CSS Accessibility)
Практичне керівництво з CSS-доступності: :focus-visible та кастомний outline, prefers-reduced-motion, prefers-color-scheme, forced-colors, sr-only техніка, контрастність кольорів, розміри touch targets та print styles.
Rendering Pipeline і CSS Performance
Як браузер перетворює CSS у пікселі: Critical Rendering Path, Layout → Paint → Composite, властивості що спричиняють reflow та repaint, GPU-прискорення через transform та opacity, will-change, content-visibility та CSS Containment.