متغیرها (Custom Properties) و سلکتورهای مدرن
متغیرها (Custom Properties) و سلکتورهای مدرن؛ آموزش CSS به زبان ساده، همراه با مثال و خروجی.
Custom Properties
:root {
--brand: #1a73e8;
--radius: 8px;
--space-1: .25rem;
--space-2: .5rem;
--space-3: 1rem;
--font-body: "Vazirmatn", system-ui, sans-serif;
}
.btn {
background: var(--brand);
border-radius: var(--radius);
padding: var(--space-2) var(--space-3);
font-family: var(--font-body);
}
- با
--شروع میشن و حساس به حروف هستن. - با
var(--name)استفاده میشن. مقدار پیشفرض (fallback):
color: var(--text, #333);
color: var(--text, var(--fallback, black));
- ارث میبرن و cascade دارن (برخلاف متغیرهای Sass که زمان کامپایلان).
- در زمان اجرا قابل تغییر با JS و CSS.
Scope و Override
:root { --bg: white; --fg: #222; }
.dark { --bg: #111; --fg: #eee; } /* هر زیردرخت .dark متغیرهای خودش رو داره */
.card { background: var(--bg); color: var(--fg); }
.btn { --btn-bg: var(--brand); background: var(--btn-bg); }
.btn.danger { --btn-bg: crimson; } /* الگوی «hook» برای variant */
تغییر با JS
document.documentElement.style.setProperty("--brand", "#e91e63");
getComputedStyle(document.documentElement).getPropertyValue("--brand");
el.style.setProperty("--progress", "40%");
.bar { width: var(--progress, 0%); }
مثال: موقعیت ماوس
card.addEventListener("pointermove", (e) => {
card.style.setProperty("--x", e.offsetX + "px");
card.style.setProperty("--y", e.offsetY + "px");
});
.card { background: radial-gradient(200px at var(--x) var(--y), #fff3, transparent); }
تمدهی (Theming)
:root { color-scheme: light dark; }
:root { --bg: #fff; --fg: #222; }
:root[data-theme="dark"] { --bg: #121212; --fg: #eee; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --bg: #121212; --fg: #eee; } }
توکنهای طراحی (Design Tokens)
:root {
/* primitive */
--blue-500: oklch(60% .2 255);
--gray-900: oklch(20% 0 0);
/* semantic */
--color-primary: var(--blue-500);
--color-text: var(--gray-900);
/* component */
--button-bg: var(--color-primary);
}
متغیر در calc و واحد
:root { --gap: 1rem; }
.x { padding: calc(var(--gap) * 2); margin-inline: calc(var(--gap) * -1); }
متغیر فقط توکن مقدار را جایگزین میکنه؛
--n: 2←calc(var(--n) * 1px)یاcalc(1px * var(--n)).
متغیر نامعتبر
اگه مقدار نامعتبر باشه، ویژگی «invalid at computed-value time» و به مقدار ارثی/پیشفرض میرسه (نه fallback).
@property — متغیر با نوع
@property --angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.spin { background: conic-gradient(from var(--angle), red, blue, red); animation: rot 3s linear infinite; }
@keyframes rot { to { --angle: 360deg; } } /* حالا قابل انیمیت است! */
syntax: <color> <length> <percentage> <number> <angle> <time> <image> *.
attr() و counter
a[href^="http"]::after { content: " ↗ " attr(href); }
.tip::after { content: attr(data-tip); }
body { counter-reset: h2; }
h2::before { counter-increment: h2; content: "بخش " counter(h2, persian) ": "; }
سلکتورهای مدرن
:is() و :where()
:is(h1, h2, h3):hover { color: var(--brand); }
:where(ul, ol) > li { margin: 0; } /* specificity صفر: راحت override میشه */
:where برای استایلهای reset/پایه؛ :is وقتی specificity مهمه.
:not() با لیست
a:not([class], [href^="#"]) { text-decoration: underline; }
li:not(:last-child) { border-bottom: 1px solid #ddd; }
:has() ⭐ (انتخاب والد)
.card:has(img) { grid-template-columns: 1fr 2fr; }
.field:has(input:invalid) label { color: crimson; }
form:has(input:checked) .submit { opacity: 1; }
body:has(dialog[open]) { overflow: hidden; } /* قفل اسکرول */
h2:has(+ p) { margin-block-end: .25rem; } /* تیتری که بعدش p هست */
li:has(> a:focus-visible) { outline: 2px solid; }
.grid:has(> :nth-child(5)) { /* وقتی ≥ ۵ آیتم داره */ }
- شمارش آیتمها:
li:first-child:nth-last-child(3), li:first-child:nth-last-child(3) ~ li. - Quantity queries ← با
:hasسادهتر.
:focus-visible و :focus-within
button:focus-visible { outline: 3px solid var(--brand); }
.form:focus-within { box-shadow: 0 0 0 3px #1a73e840; }
:target, :checked, :indeterminate, :default, :read-only
section:target { background: lightyellow; } /* #id در URL */
input:checked + label { font-weight: bold; }
dialog:modal { }
:popover-open { }
:fullscreen { }
:dir(rtl) .icon { transform: scaleX(-1); } /* جهت متن */
:lang(fa) { font-family: Vazirmatn; }
:user-invalid { border-color: crimson; }
:any-link { }
:nth-child(… of …)
li:nth-child(2 of .active) { } /* دومین .active */
Attribute و case
[data-state="open" i] { }
Pseudo-element های مدرن
::selection { }
::first-line { }
::marker { }
::placeholder { }
::file-selector-button { }
::backdrop { }
::target-text { }
::highlight(search) { } /* CSS Custom Highlight API */
::view-transition-old(root) { }
::details-content { }
& در Nesting
در بخش Nesting.
Specificity با این سلکتورها
:is(#a, .b) p { } /* = specificity بالاترین: #a → (1,0,1) */
:where(#a, .b) p { } /* = (0,0,1) */
:has(.x) { } /* = specificity داخلش */
:not(.x) { } /* = specificity داخلش */
الگوی «Lobotomized Owl»
.stack > * + * { margin-block-start: 1rem; }
/* مدرنتر: */
.stack { display: grid; gap: 1rem; }
تمرین
۱. سیستم طراحی کوچک با توکنهای رنگ/فاصله/شعاع و دو تم light و dark بساز. ۲. با :has فرمی بساز که اگه فیلدی نامعتبر باشه، دکمهی submit غیرفعال بهنظر برسه. ۳. دکمهای با --btn-bg و چند variant بساز. ۴. با @property یک border گرادیانی چرخان بساز.