متغیرهای CSS: چرا هنوز از آنها استفاده نمیکنید؟
متغیرهای CSS چطور کد را تمیزتر، تمپذیر و قابل نگهداری میکنند؟ راهنمای عملی custom properties، اسکوپ، تم تیره، تعامل با جاوااسکریپت و اشتباهات رایج در پروژههای واقعی.
یادم میآید در یک پروژهی بازطراحی، تیم تصمیم گرفت رنگ برند را از یک آبی خاص به سبز تغییر دهد. در فایل CSS، ۲۳۷ مورد از آن رنگ آبی پراکنده بود، بعضی با هگز، بعضی با rgba، بعضی در قالب متغیر Sass اما در سه فایل مختلف. سه روز کار طول کشید تا همهی آنها پیدا و تغییر داده شوند، و بعد از آن هم مدام رنگهای جاافتاده پیدا میشد. اگر آن پروژه از متغیرهای CSS استفاده میکرد، این کار یک دقیقهای بود. آن تجربه، مرز بین «CSS مینویسم» و «CSS حرفهای مینویسم» را برای من روشن کرد.
متغیرهای CSS، که با نام رسمیتر CSS Custom Properties هم شناخته میشوند، یک قابلیت بومی زبان هستند که مدتها زیر سایهی پیشپردازندههایی مثل Sass و Less نادیده گرفته میشدند. اما آنها یک فرق بنیادی با متغیرهای پیشپردازنده دارند: در زمان اجرا زندهاند و میتوانند تغییر کنند. اگر در مسیر آموزش CSS از صفر هستید، این مرحلهای است که کد شما را از «کار میکند» به «قابل نگهداری» میرساند.
چرا متغیرهای CSS یک تحول واقعی هستند؟
در روزهای اول CSS، هر مقدار در کد بهصورت مستقیم نوشته میشد. اگر یک رنگ یا فاصلهی خاص در چندین جا استفاده میشد، به همان تعداد کپی از مقدار در کد وجود داشت. تغییر آن به یک جستجوی سراسری و جایگزینی تبدیل میشد که در پروژههای بزرگ، خطرناک و کند بود.
متغیرهای CSS این مشکل را از ریشه حل کردند. یک مقدار را یک بار تعریف میکنید، در همهی جاهای کد از آن استفاده میکنید، و اگر لازم شد تغییر دهید، فقط یک خط را عوض میکنید. سه دلیل که در پروژههایم بیشترین اثر را داشته:
- کاهش تکرار: در یک پروژهی نمونه که بازنویسی کردم، تعداد اعلانهای رنگی از چند صد خط به کمتر از سی خط رسید. همان ظاهر، نصف کد.
- پشتیبانی از تم داینامیک: قابلیتی که فقط با متغیرهای CSS ممکن است — تغییر تمام رنگهای سایت با یک تغییر کوچک در سطح
:rootیا حتی از طریق JavaScript. - خوانایی بهتر: جای
#1a73e8، میبینیدvar(--color-primary). کد به زبانی نزدیکتر به توصیف انسانی تبدیل میشود.
اگر در مسیر فلکس باکس در CSS و گرید در CSS هستید، متغیرها مکمل طبیعی آنها هستند؛ چون چیدمان را با Flexbox و Grid میسازید و مقادیر پایه (رنگ، فاصله، اندازهی فونت) را با متغیر مدیریت میکنید.
یک رنگ که ۲۰ بار در کد تکرار شده باشد، یک تصمیم است که ۲۰ نقطهی تغییر دارد؛ یک متغیر، همان تصمیم را به یک نقطهی تغییر تبدیل میکند.
تفاوت بنیادی با متغیرهای Sass و Less
یکی از پرتکرارترین سؤالات در تیمهای فنی این است: «اگر Sass داریم، چرا CSS Variables؟» پاسخ در یک جمله: متغیر Sass در زمان کامپایل حل میشود، متغیر CSS در زمان اجرا زنده است.
وقتی Sass را کامپایل میکنید، متغیرها جایگزین مقادیر ثابت میشوند و دیگر وجود ندارند. یعنی بعد از کامپایل، خروجی یک CSS ثابت است. متغیر CSS اما در مرورگر وجود دارد و در هر لحظه میتواند تغییر کند. سه نتیجهی مهم این تفاوت:
| موقعیت | متغیر CSS | متغیر Sass |
|---|---|---|
| تغییر در زمان اجرا | بله، از طریق JS یا media query | خیر، مقدار ثابت شده |
| اسکوپپذیری | در سلسلهمراتب DOM | در سطح فایل یا کامپایل |
| کار با DevTools | قابل دیدن و تغییر زنده | دیده نمیشود |
| محاسبات ریاضی | با calc() | در زمان کامپایل |
| پشتیبانی مرورگر | همهی مرورگرهای امروزی | وابسته به build pipeline |
در پروژههای اخیرم، Sass و CSS Variables را بهطور موازی بهکار میبرم: Sass برای منطق کامپایلمحور (مثل توابع و mixinها) و CSS Variables برای مقادیر پویا (مثل رنگهای تم). این ترکیب، پرقدرتترین حالت را میسازد. برای مطالعهی مسیر تکامل CSS و ابزارهای امروزی، CSS مدرن از Flexbox تا Grid را ببینید.
اولین متغیر: تعریف و استفاده
سینتکس متغیرهای CSS در نگاه اول ساده است. با دو خط -- تعریف میشود و با تابع var() استفاده میشود:
:root {
--color-primary: #0055ff;
--space-md: 1rem;
--radius: 6px;
}
.button {
background: var(--color-primary);
padding: var(--space-md);
border-radius: var(--radius);
}
سه نکتهی ظریف که تازهکارها را گیج میکند:
- نامگذاری: نامها به حساسیت حروف حساساند و باید با
--شروع شوند. یک فضای اضافه یا کاراکتر مخفی میتواند باعث شود متغیر کار نکند و مرورگر بیسروصدا مقدار را نادیده بگیرد. - حل شدن در زمان اجرا: مقدار متغیر در لحظهی استفاده از DOM خوانده میشود، نه در لحظهی تعریف. یعنی اگر بعداً متغیر را تغییر دهید، همهی جاهایی که از آن استفاده میکنند بهطور خودکار بهروز میشوند.
- مقدار پیشفرض نیستند: اگر متغیری تعریف نشده باشد،
var()بدون مقدار جایگزین بیاعتبار میشود و آن اعلان بهکلی نادیده گرفته میشود. این یک دام مهم است که در بخش fallback به آن میرسم.
اسکوپ در متغیرها: cascade در عمل
یکی از قابلیتهای منحصربهفرد متغیرهای CSS که در Sass وجود ندارد، اسکوپ در سلسلهمراتب DOM است. یعنی میتوانید متغیری را در سطح :root تعریف کنید، اما در یک بخش خاص از صفحه مقدار آن را بازنویسی کنید:
:root {
--color-bg: #ffffff;
}
.dark-section {
--color-bg: #1a1a1a;
}
.card {
background: var(--color-bg);
}
کارتهایی که داخل .dark-section هستند، پسزمینهی تیره میگیرند و بقیه سفید میمانند. این رفتار در Sass غیرممکن است چون همهی متغیرها در زمان کامپایل نهایی شدهاند. کاربرد واقعی این اسکوپ در پروژههای وردپرسی: میتوانید یک قالب را طوری بنویسید که بخشهای مختلف سایت (هدر، فوتر، سایدبار) رنگبندی مستقل داشته باشند بدون آنکه یک کلاس اضافه در HTML بسازید. اگر در پروژهای با قالبهای وردپرسی کار میکنید، این الگو در کدنویسی اختصاصی برای قالب وردپرس و قالب وردپرس چیست بحث شده است.
پیادهسازی تم تیره با متغیرها
الگویی که در چند پروژهی اخیر بهکار بردهام و بسیار تمیز جواب داده: تعریف یک لایهی متغیر پایه و بازنویسی آن در حالت تم تیره:
:root {
--color-bg: #ffffff;
--color-text: #222222;
--color-primary: #0055ff;
}
[data-theme="dark"] {
--color-bg: #121212;
--color-text: #eaeaea;
--color-primary: #4a90e2;
}
body {
background: var(--color-bg);
color: var(--color-text);
}
حالا برای فعالسازی تم تیره، فقط کافی است روی عنصر html صفت data-theme="dark" را تنظیم کنید. تمام رنگهای سایت بهطور خودکار عوض میشوند، بدون یک خط CSS اضافه. این یک قابلیتی است که فقط با متغیرهای CSS ممکن است و در پروژههای امروزی جزو انتظارات کاربران است. برای مطالعهی این که چطور تم تیره را با جزئیات بیشتر پیادهسازی کنید، ترفندهای CSS برای طراحی سریعتر را ببینید. اگر روی پروژهای با وردپرس کار میکنید، پیشنهاد میکنم تنظیمات پنل و تم را با قالب چایلد ترکیب کنید تا با آپدیت قالب از دست نروند.
تم تیره با متغیرهای CSS، دو خط کد و یک صفت در HTML است؛ با هر رویکرد دیگر، یک بازنویسی کامل.
تعامل با JavaScript و تغییر زنده
مزیت جادویی متغیرهای CSS در تعامل با JavaScript آشکار میشود. دو روش:
۱. تغییر از طریق style
document.documentElement.style.setProperty('--color-primary', '#ff6600');
همین یک خط، تمام رنگهای اصلی سایت را در لحظه عوض میکند. بدون رندر مجدد، بدون بارگذاری دوبارهی استایلشیت. این الگو را در پروژههای پنل ادمین برای تمسازی داینامیک استفاده کردهام.
۲. خواندن از طریق getComputedStyle
const primary = getComputedStyle(document.documentElement)
.getPropertyValue('--color-primary')
.trim();
این الگو برای همراستایی جاوااسکریپت با CSS کاربردی است. مثلاً وقتی میخواهید نمودار یا گرافیک SVG را با همان رنگهای سایت رسم کنید. در بهینه سازی جاوااسکریپت توضیح دادهام که هر تماس با getComputedStyle یک layout recalculation تحریک میکند، پس بهتر است نتیجه را در متغیر جاوااسکریپت کش کنید و در هر فریم دوباره نخوانید.
مقدار جایگزین (fallback) و پشتیبانی مرورگر
پشتیبانی مرورگرها از متغیرهای CSS امروز کامل است؛ حتی مرورگرهای قدیمی موبایل که در ایران زیاد دیده میشوند، این قابلیت را از سالها پیش پشتیبانی میکنند. اما یک الگوی دفاعی که در پروژهها بهکارم آمده: استفاده از مقدار جایگزین در var():
.card {
color: var(--color-text, #333);
}
اگر --color-text تعریف نشده باشد، مقدار #333 استفاده میشود. این نکتهی کوچک، در پروژههایی که با کامپوننتهای مستقل کار میکنید بسیار مفید است؛ چون هر کامپوننت میتواند بدون نگرانی از تعریف شدن متغیر در والد، خودش مقدار پیشفرض داشته باشد.
نکتهی مهم دیگر: fallback در var() فقط زمانی مفید است که متغیر تعریف نشده باشد، نه وقتی مقدار نامعتبر دارد. اگر متغیر تعریف شده باشد اما مقدارش بیاعتبار (مثلاً یک رنگ نامعتبر)، fallback استفاده نمیشود و اعلان بهکلی نادیده گرفته میشود.
Design Tokens: یک سطح بالاتر از متغیر
Design Tokens یک مفهوم است که در سالهای اخیر محبوب شده و متغیرهای CSS پایهی پیادهسازیاش در وب هستند. ایده این است: تمام تصمیمهای بصری (رنگ، فاصله، شعاع گوشه، سایه، تایپوگرافی) بهصورت متغیرهای نامدار در یک لایهی مستقل تعریف شوند و کامپوننتها فقط از این نامها استفاده کنند. یک نمونهی حداقلی که در پروژههای شخصی استفاده میکنم:
:root {
/* رنگها */
--color-primary: #0055ff;
--color-bg: #ffffff;
--color-surface: #f5f5f5;
/* فاصلهها */
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 2rem;
/* شعاع و سایه */
--radius: 6px;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
}
مزیت این لایهبندی: وقتی تیم طراحی تصمیم گرفت فاصلههای استاندارد عوض شوند، فقط چند خط تغییر میکند و همهی کامپوننتها خودکار بهروز میشوند. اگر در پروژهای با سیستم طراحی کار میکنید، این الگو پایهی همان مفهوم Design System است. برای درک این مفهوم، سیستم طراحی چیست را ببینید. اگر روی پروژهای وردپرسی هستید که چند نفر روی آن کار میکنند، همین لایهی متغیرها میتواند تفاوت بین یک پروژهی منظم و یک آشپزخانهی بههمریخته باشد.
اشتباهاتی که در پروژهها دیدم
- نامگذاری غیرمنظم: دیدم پروژهای که در آن متغیرها با نامهای
--blue1،--mainColor،--c-primaryو--colorBrandدر چهار فایل مختلف تعریف شده بودند. این یعنی متغیر دارید اما مشکل اصلی (نبود استاندارد) را حل نکردهاید. پیشنهاد من: از همان روز اول یک پیشوند معنادار انتخاب کنید و به آن پایبند بمانید. - استفاده بیرویه از متغیر برای همهچیز: متغیر برای تصمیمهای پایهی طراحی است، نه برای هر مقدار خاص در هر کامپوننت. متغیری که فقط یکبار در یک کامپوننت استفاده میشود، فقط یک لایهی اضافه است.
- نادیده گرفتن
:root: بعضی پروژهها متغیرها را درhtmlیاbodyتعریف میکنند. این کار در نگاه اول تفاوت ندارد اما از نظر معنایی،:rootاستاندارد است و انتخاب اول تیمهای فنی. - تکرار مقدار در fallback: اگر
var(--color-primary, #0055ff)بنویسید و همان رنگ هگز را در جای دیگری از کد دوباره استفاده کنید، در واقع یک متغیر پنهان دیگر ساختهاید. مقدار fallback باید حداقلی و صرفاً دفاعی باشد، نه بخشی از طراحی. - استفاده از متغیر برای مقادیری که در Media Queries تغییر میکنند: یک دام ظریف. متغیرهای CSS در
@mediaقابل تعریف مجدد هستند، اما این کار در بعضی مرورگرها باعث recalculate پیچیدهتری میشود. اگر روی کارایی سایت حساس هستید، مقادیر ریسپانسیو را مستقیم در media query بنویسید، نه از طریق تغییر متغیر.
بخشی از این اشتباهات در ترفندهای CSS برای طراحی سریعتر هم فهرست شده است.
کارایی و رفتار موتور رندر
یکی از پرتکرارترین سؤالاتی که در تیمهای فنی میشنوم: «آیا متغیرهای CSS سایت را کند میکنند؟» پاسخ کوتاه: در سایتهای معمولی نه، اما در سه سناریو اثر محسوس میشود.
- تغییر متغیر در حین انیمیشن: هر بار که متغیری که در چندین عنصر استفاده میشود تغییر کند، مرورگر باید همهی آن عناصر را دوباره style کند. اگر این تغییر در هر فریم انیمیشن اتفاق بیفتد، هزینهی محاسبه میتواند بهسرعت بالا برود. برای انیمیشن، بهجای متغیر، از
transformوopacityاستفاده کنید. - زنجیرهی طولانی متغیرها: یک متغیر که به متغیر دیگری ارجاع میدهد که خودش به یک متغیر سوم و... در نهایت، موتور رندر باید کل زنجیره را برای هر عنصر resolve کند. یک یا دو سطح از زنجیره مشکلی ندارد، اما ده سطح نه.
- تعریف متغیر در سطح
:rootبرای همهچیز: اگر متغیری فقط در یک کامپوننت استفاده میشود، تعریفش در سطح:rootباعث میشود که همهی عناصر درخت DOM آن را در حافظهی محاسباتی موتور رندر داشته باشند. تعریف متغیر در سطح کامپوننت، هم سبکتر است و هم منطقیتر.
برای مطالعهی مکانیزم دقیق این رفتارها، بهینهسازی CSS و بهینهسازی سرعت سایت را ببینید. اگر در پروژهی وردپرسی با قالب سنگین کار میکنید، توجه داشته باشید که متغیرهای CSS یکی از کمهزینهترین قابلیتهای زبان هستند اما اگر تعدادشان از چند صد رد شود، همان هزینهی اضافهی حجم فایل هم به آن اضافه میشود. مهاجرت به قالب سبک در این حالت، بیشترین اثر را دارد.
لایهای پایینتر از سینتکس
اگر میخواهید به لایهی مهندسی نفوذ کنید، سه مفهوم را بشناسید:
- Cascading Value و Computed Value: متغیرهای CSS برخلاف خاصیتهای معمولی، در دو مرحله پردازش میشوند. ابتدا مقدار واقعی متغیر در هر عنصر با cascade تعیین میشود (specified value)، سپس هنگام استفاده در یک خاصیت، این مقدار resolve میشود (computed value). این دو مرحلهی جدا توضیح میدهد چرا متغیرها بهطور زنده تغییر میکنند اما خاصیتهایی که از آنها استفاده میکنند بهطور خودکار بهروز نمیشوند مگر در مرحلهی بعدی recalculate.
- Custom Property Registration با
@property: با at-rule جدید@propertyمیتوانید برای یک متغیر، type، initial value و inheritance را تعریف کنید. این قابلیت جدید در مرورگرهای مدرن، امکان انیمیت کردن مقادیر متغیر را ممکن میکند (چیزی که در گذشته غیرممکن بود). مثلاً میتوانید یک متغیر از نوعcolorتعریف کنید و آن را در انیمیشن بهطور نرم تغییر دهید. - Interaction با CSS-in-JS و فریمورکها: در React و Vue، اگر از Styled Components یا Emotion استفاده میکنید، هر رندر ممکن است کلاسهای جدیدی تولید کند که مقدار متغیر را inline میکنند. این کار در پروژههای بزرگ با چندین تم، بهسرعت به مشکل حجم CSS تبدیل میشود. راهحل مدرن: تعریف یک لایهی متغیر در
:rootو تغییر از طریق JavaScript، بدون تولید کلاس جدید. برای مطالعهی همراستایی این لایه با کارایی سایت، بهینه سازی جاوااسکریپت را ببینید.
یک تجربهی واقعی از پروژهی داشبوردی که چند تم رنگی داشت: در ابتدا هر تم با یک فایل CSS جدا پیاده شده بود که با تغییر کاربر، کل استایلشیت عوض میشد. با انتقال به متغیرهای CSS و تعریف تمها در [data-theme]، نهفقط حجم کل CSS نزدیک به نصف کاهش پیدا کرد، بلکه سوییچ بین تمها بهطور محسوسی روانتر شد. برای مطالعهی مبانی تکنیکال که به این تصمیم منجر شد، استانداردهای HTML و CSS و انتخابگرهای CSS را در کنار این بخش بخوانید.
متغیرها زمانی که در جای درست تعریف شوند، قیمتشان صفر است؛ زمانی که در جای اشتباه تکرار شوند، هزینهشان بیشتر از سودشان میشود.
آخرین حرف این مسیر
متغیرهای CSS را میتوان در یک جمله خلاصه کرد: «یک سطح انتزاع بومی که تصمیمهای بصری را از پیادهسازی جدا میکند.» سه درس که در پایان این مسیر روی آنها تأکید میکنم:
- از یک لایهی پایه شروع کنید. همان روز اول پروژه، یک بلوک
:rootبا رنگها، فاصلهها و شعاعهای پایه بنویسید. این یک سرمایهگذاری کوچک است که در طول عمر پروژه چندین برابر برمیگردد. - نامگذاری را جدی بگیرید. تفاوت بین
--blue1و--color-primary، در روز اول کوچک به نظر میرسد اما در شش ماه بعد، تفاوت بین کدی که هر توسعهدهنده میفهمد و کدی که نیاز به توضیح دارد. - تم داینامیک را جدی بگیرید. تم تیره، انتخاب کاربر، یا هر تغییری که در زمان اجرا اتفاق میافتد، با متغیرها رایگان است. اگر این قابلیت را در معماری امروز نادیده بگیرید، در آینده بازنویسی کامل لازم است.
مسیر یادگیری CSS با این نوشته تمام نمیشود. اگر میخواهید مرحلهی بعدی را بردارید، بهینهسازی CSS و ریسپانسیو با CSS قدمهای منطقی بعدی هستند. اگر هم به سمت معماری بزرگتر میروید، سیستم طراحی چیست و ابزارهای ساخت سیستم طراحی دید وسیعتری میدهند.
اگر در پروژهای با یک ترفند جالب از متغیرهای CSS به نتیجهای رسیدهاید — مثلاً یک تم داینامیک یا یک الگوی Design Token که کد را نصف کرده — تجربهی خودتان را در دیدگاه بنویسید. مخصوصاً اگر در پروژهای وردپرسی یا React از آن برای حل یک مشکل واقعی استفاده کردهاید، جزئیات همان سناریو برای خوانندهی بعدی از هر توضیح کلی ارزشمندتر است. 🎨