SN.
همه مقالات
فرانت‌اند۱۶ مرداد ۱۴۰۵۹ دقیقه

ارتقای Tailwind CSS v4 بدون Regression؛ تغییر زیرساخت بدون خراب کردن UI

Tailwind v4 را مثل Redesign یا Search & Replace جلو نمی‌برم؛ اول Visual Baseline می‌سازم، بعد Tokenها، Build و رفتار Responsive/RTL را مرحله‌ای مهاجرت می‌دهم.

نمای نزدیک کد CSS روی مانیتور تیره

ارتقای یک Dependency زیرساختی برای من وقتی موفق است که کاربر در پایان تغییر ناخواسته‌ای نبیند.

Tailwind CSS v4 مثال خوبی است. تغییر فقط چند Utility Class نیست؛ مدل Configuration، Theme Variableها و Build Integration نسبت به نسل قبلی متفاوت شده است. اگر هم‌زمان با این مهاجرت Typography، spacing یا Componentها را هم «بهتر» کنیم، بعد از اولین Regression دقیقاً نمی‌دانیم کدام تغییر مقصر بوده است.

برای همین Upgrade را از Redesign جدا می‌کنم و قبل از هر تغییر یک Visual Contract می‌سازم.

قبل از Dependency، محصول را Baseline می‌کنم

اول مشخص می‌کنم چه چیزهایی نباید تغییر کنند. برای یک Front-End Production معمولاً این فهرست را دارم:

  • رنگ‌ها و Design Tokenهای اصلی؛
  • Font Family، Weight، Line-height و نحوه Load شدن Font؛
  • عرض Containerها و Breakpointها؛
  • Grid و Stack شدن Componentها؛
  • Border، Radius و Shadowهای مهم؛
  • Hover، Focus-visible، Disabled و Active Stateها؛
  • رفتار RTL/LTR؛
  • صفحه‌های حساس مثل Header، Checkout، Dashboard یا Modal؛
  • Warningهای Build و خروجی Production موجود.

اگر ابزار Visual Regression خودکار وجود نداشته باشد، حداقل Screenshot مرجع برای Viewportهای اصلی و چند Flow کلیدی ثبت می‌کنم. هدف این نیست که Pixel Diff کامل بسازم؛ هدف این است که حافظه بصری جای Baseline واقعی را نگیرد.

در Tailwind v4 اول مدل Token را می‌فهمم

Tailwind v4 مسیر CSS-first پررنگ‌تری دارد و Theme Variableها می‌توانند با @theme تعریف شوند تا هم CSS Variable باشند و هم Utilityهای مرتبط را تولید کنند.

@import "tailwindcss";

@theme {
  --color-accent: #ff6a00;
  --breakpoint-3xl: 120rem;
}

در Migration، این Syntax برای من هدف نیست؛ یک سؤال مهم‌تر مطرح می‌کند: Tokenهای پروژه امروز واقعاً کجا زندگی می‌کنند؟

در پروژه قدیمی ممکن است رنگ Brand در Config، CSS Variable، Arbitrary Value و چند Component با چهار نام مختلف تکرار شده باشد. مهاجرت فرصت خوبی برای مشخص کردن Source of Truth است، اما نه برای عوض کردن خود Design.

ابتدا Mapping می‌سازم: Token قدیمی به Token جدید. بعد Consumerها را جابه‌جا می‌کنم. اگر هم‌زمان مقدار Token را هم تغییر بدهم، Upgrade و Redesign به هم گره می‌خورند.

Migration را در Sliceهای کوچک انجام می‌دهم

من معمولاً ترتیب کار را این‌طور نگه می‌دارم:

  1. Build Integration جدید را بالا می‌آورم.
  2. CSS پایه و Fontها را بدون تغییر ظاهری منتقل می‌کنم.
  3. Tokenها و Custom Utilityهای ضروری را Mapping می‌کنم.
  4. Componentهایی که Build یا Utility آن‌ها شکسته است اصلاح می‌کنم.
  5. Viewportها و Stateهای تعاملی را مقایسه می‌کنم.
  6. فقط بعد از رسیدن به Parity، Cleanupهای غیرضروری را بررسی می‌کنم.

این ترتیب عمداً کندتر از یک Replace بزرگ به نظر می‌رسد، اما Debug کردن Regression را بسیار سریع‌تر می‌کند.

Typography یکی از اولین جاهایی است که Regression پنهان می‌شود

Font فقط font-family نیست. تغییر Build ممکن است Load Order، Variable Font، Fallback یا Preflight را طوری تغییر دهد که Heading چند Pixel متفاوت Wrap شود.

برای پروژه دو‌زبانه فارسی/انگلیسی این اختلاف مهم‌تر است. یک Scale که برای Fraunces خوب است الزاماً برای Yekan Bakh مناسب نیست. Line-height فشرده انگلیسی ممکن است در فارسی Clip یا فشردگی ایجاد کند.

در Upgrade زیرساختی این موارد را جداگانه چک می‌کنم:

  • Font واقعاً از همان Asset قبلی Load می‌شود؛
  • Weightهای مورد استفاده موجودند؛
  • line-height و Letter-spacing ناخواسته Reset نشده‌اند؛
  • Headingهای طولانی در عرض‌های مرزی Wrap قابل قبول دارند؛
  • Fallback قبل از Load Font باعث Jump قابل توجه نمی‌شود.

اگر Typography باید Redesign شود، آن را Task جدا ثبت می‌کنم؛ نه اینکه داخل Migration پنهانش کنم.

Breakpoint فقط یک عدد در Config نیست

تغییر Breakpoint ممکن است صفحه را در 1024px یا 1280px کاملاً متفاوت کند، حتی اگر Desktop بزرگ سالم باشد.

من Viewportهای مرزی را عمداً تست می‌کنم: کمی قبل و بعد از هر Breakpoint. خیلی از Bugها دقیقاً جایی ظاهر می‌شوند که Grid هنوز Desktop است اما فضای واقعی برای Childها کافی نیست.

مواردی که دنبال می‌کنم:

  • min-widthهایی که باعث Horizontal Scroll می‌شوند؛
  • Grid Columnهایی که با متن بلند جمع نمی‌شوند؛
  • hidden/flexهایی که در Breakpoint اشتباه فعال می‌شوند؛
  • Imageهایی که Aspect Ratio یا object-fit متفاوت گرفته‌اند؛
  • Sticky Elementهایی که با Header هم‌پوشانی پیدا می‌کنند.

Responsive QA فقط چند Screenshot در 1440 و 390 نیست؛ رفتار بین آن‌ها هم مهم است.

Hover و Focus را مثل Typography Baseline می‌کنم

Regression همیشه Layout نیست. Utility یا Variant تغییریافته می‌تواند Hover، Focus-visible، Disabled یا Group State را از بین ببرد و صفحه در Screenshot عادی کاملاً سالم به نظر برسد.

برای Componentهای تعاملی این Stateها را مشخص تست می‌کنم:

  • Link و Button با Keyboard؛
  • Focus Ring و Contrast؛
  • Hover فقط روی Deviceهایی که آن را دارند؛
  • Disabled State بدون کاهش بیش از حد خوانایی؛
  • Group/Peer interaction؛
  • Transitionهایی که Property درست را Animate می‌کنند.

اگر پروژه Accessibility دارد، Upgrade نباید آن را به Feature اختیاری تبدیل کند.

RTL جایی است که Physical Utilityها خودشان را نشان می‌دهند

در پروژه فارسی صرف dir="rtl" کافی نیست. Classهایی که بر اساس left/right نوشته شده‌اند ممکن است بعد از Refactor دقیقاً جهت اشتباه را بگیرند.

من هنگام Migration دنبال Physical Assumptionها می‌گردم:

  • ml/mr در جایی که ms/me منطقی‌تر است؛
  • left/right برای Badge یا Decoration؛
  • Arrowهایی که باید بر اساس Locale عوض شوند؛
  • Gradientی که جهت Composition آن به محل تصویر وابسته است؛
  • متن فنی LTR داخل Paragraph فارسی.

اما هدف Migration «RTL کردن دوباره سایت» نیست. فقط مطمئن می‌شوم مدل جدید همان رفتار تأییدشده را حفظ می‌کند.

Build Configuration را جدا از UI Audit می‌کنم

Tailwind v4 با CSS و Build Tool مدرن‌تر یکپارچه شده، اما هر پروژه Legacy فرض‌های خودش را دارد. قبل از حذف Config یا Plugin قدیمی بررسی می‌کنم آیا واقعاً مصرف می‌شود.

موارد مهم:

  • PostCSS/Vite integration فعلی؛
  • Pluginهای Tailwind یا Custom Utilityها؛
  • Source Detection و Classهای داینامیک؛
  • CSS Preprocessorهای قدیمی؛
  • Import Order؛
  • Minification و Production Output؛
  • Browser Support پروژه.

Tailwind v4 به قابلیت‌های CSS مدرن تکیه دارد و برای Browserهای قدیمی محدودیت بیشتری دارد. اگر محصول الزام Browser قدیمی دارد، «آخرین نسخه بودن» دلیل کافی برای Upgrade نیست. Compatibility Requirement بخشی از تصمیم فنی است.

Classهای داینامیک را قبل از Build Production پیدا می‌کنم

یکی از Bugهای خطرناک Utility-first این است که Class در Development به‌نظر درست بیاید اما Source Detection نتواند رشته ساخته‌شده در Runtime را تشخیص دهد.

به‌جای ساختن نام Class با Template Stringهای باز، Variantهای محدود را Map می‌کنم:

const toneClass = {
  danger: "text-red-500 border-red-500/30",
  success: "text-emerald-500 border-emerald-500/30",
};

این الگو فقط برای Tailwind v4 نیست؛ Dependency Upgrade زمان خوبی برای پیدا کردن فرض‌های Build است که قبلاً تصادفی کار می‌کردند.

Checklist Regression من بعد از Migration

Visual

  • Color و Tokenها با Baseline یکی هستند.
  • Typography و Wrapping در زبان‌های پروژه تغییر ناخواسته ندارد.
  • Border، Radius، Shadow و Opacity ثابت مانده‌اند.
  • Image Crop و Aspect Ratio درست‌اند.

Responsive

  • Mobile، Tablet و Desktop سالم‌اند.
  • عرض‌های نزدیک Breakpointها Horizontal Scroll ندارند.
  • Grid و Navigation در نقاط مرزی تست شده‌اند.
  • RTL/LTR در Layoutهای حساس درست است.

Interaction

  • Hover، Focus-visible، Active و Disabled کار می‌کنند.
  • Animation و Transition از بین نرفته‌اند.
  • Keyboard Navigation حفظ شده است.

Build

  • Production Build موفق است.
  • Warning جدید بدون توضیح وجود ندارد.
  • Asset Path و Font Path درست‌اند.
  • Dependency قدیمی‌ای که هنوز لازم است تصادفی حذف نشده است.
  • Browser Target با Requirement محصول سازگار است.

چه زمانی Upgrade را عقب می‌اندازم؟

اگر پروژه Browser قدیمی اجباری دارد، Release حساس نزدیک است، Test Coverage کافی نیست یا تیم هنوز Baseline ظاهری ندارد، تأخیر Upgrade می‌تواند تصمیم حرفه‌ای‌تری باشد.

هدف مهندسی استفاده از جدیدترین نسخه در کوتاه‌ترین زمان نیست. هدف کم کردن ریسک و نگه داشتن محصول در وضعیتی است که توسعه بعدی قابل پیش‌بینی باشد.

جمع‌بندی

Tailwind CSS v4 برای من بیشتر از یک موضوع Syntax است؛ یک Case Study خوب برای تغییر زیرساخت بدون تغییر محصول است.

اول Baseline، بعد Mapping، سپس Migration مرحله‌ای و در پایان Cleanup. وقتی این ترتیب حفظ شود، می‌توان Build و Design Tokenها را مدرن کرد بدون اینکه UI فعلی هزینه آزمایش ابزار جدید را پرداخت کند.