對設計系統來說,一致性和理解性就是一切。一個好的設計系統透過實現它的程式碼的配置來確保實現的一致性。它需要是:
使用我的預設 React 堆疊和 Tailwind,我將向您展示如何設定自己的版式、顏色和間距預設值,而不僅僅是區分應用程式外觀和感覺的起點。更重要的是,它大大減少了我們需要編寫和維護的程式碼,從而減少了以系統化、一致和無錯誤的方式實現樣式的精神負擔。
我將從我經常看到的一個主要批評開始,然後分解我用來解決它的一系列配置步驟。
Tailwind 讓開發人員可以輕鬆編寫樣式,這對於快速原型設計非常有用。但這種輕鬆並不能保證良好的設計或可擴展、可維護的設計系統。
像 Tailwind 這樣的預設和零配置工具是基礎設施速度層,可以為建置創造更多時間。但是,如果您要擴展一個使用設計系統來區分自己的應用程序,則不能僅僅依賴“像午餐一樣免費”的開箱即用配置。
如果您使用預設的 Tailwind 配置運行並將樣式管理推送到組件上的類應用程序,結果通常是一堆難以推理的類分佈在組件中,偽裝成設計系統。
上面就是一個很好的例子。它幾乎難以辨認,需要大量時間才能理解,更不用說操縱了。嘗試這樣做很可能會導致重複和錯誤,從而逐漸偏離整個應用程式的設計一致性。
很容易將您的設計類別合併到單一 className 中。但要這麼做並不容易。
易用性需要權衡。使用別人的標準意味著依賴他們的專業知識。這可能是有益的,但也可能是個陷阱。讓我們退後一步,思考一下設計系統的基礎知識包含哪些內容:
在 React with Tailwind 的背景下,這些和許多其他設計系統元素都在 Tailwind 配置中設置,我們可以對其進行自訂。
{/* 更漂亮-忽略 */}
const config = { theme: { fontSize: { /* ... */ }, colors: { /* ... */ }, spacing: { /* ... */ }, }, };
您是否曾經努力記住小文本的正確字母間距?如果您可以設定一次然後忘記它怎麼辦?
我們可以直接在 tailwind.config 中將前導(行高)和追蹤(字母間距)設定為每個字體大小元組的參數。這意味著當我們使用字體大小類別時,我們不需要設定行距或追蹤。無需記住(或無法找到)小文本的字母間距是多少。
fontSize: { small: [ "13px", { lineHeight: 1.5, letterSpacing: "0.015em" }, ], base: [ "16px", { lineHeight: 1.5, letterSpacing: 0 }, ], }
現在使用 text-small 設定字體大小、行高和字母間距。將核心印刷元組封裝在一個類別中,將這些值的實作集中到配置中,而不是跨程式碼庫。可維護性的巨大勝利。
/* 13px/1.5 with 0.015em letter-spacing */ <div className="text-small" />
我們可以使用 CSS 變數在 :root 和 html.dark 範圍下設定響應式顏色。這意味著我們編寫和管理一個類,例如 bg-canvas,而不是兩個類,例如 bg-gray-100 dark:bg-gray-800。
@import "@radix-ui/colors/gray.css"; @import "@radix-ui/colors/gray-dark.css"; :root { --color-gray-base: var(--gray-1); --color-gray-bg: var(--gray-3); --color-gray-line: var(--gray-4); --color-gray-border: var(--gray-5); --color-gray-solid: var(--gray-10); --color-gray-fill: var(--gray-12); }
因為我在這裡使用 Radix Colors,所以我不需要設定 .dark 範圍,因為這已經為我完成了。如果您不喜歡基數顏色,您可以自訂它們、使用其他庫或編寫自己的庫。
然後在 Tailwind 配置中設定 CSS 變數。
colors: { canvas: "var(--color-gray-base)", background: "var(--color-gray-bg)", line: "var(--color-gray-line)", border: "var(--color-gray-border)", solid: "var(--color-gray-solid)", fill: "var(--color-gray-fill-contrast)", }
現在使用 bg-canvas 在淺色或深色模式下設定適當的顏色。在程式碼庫中刪除這種重複可以將顏色管理集中到我們的配置中,而不是將其分散到元件上的類別的實作中。認知和可維護性的巨大勝利。
/* sets --gray-1 as #fcfcfc on :root or #111111 on html.dark */ <div className="bg-canvas" />
我提倡顏色和字體大小的語義名稱,因為語義命名是一種將含義與使用聯繫起來的強制功能。這樣做可以消除實現猜測工作並減少錯誤。
我看過無數項目,其中背景都使用了不一致的gray-50、gray-100或gray-200。透過定義一種稱為背景的顏色可以輕鬆解決這個問題。
In the same way, it is easier to remember the names for dark and light text colors when they are called fill and solid. It's harder and more error-prone when they're called gray-900 and gray-600 because then you have to remember specifically that it wasn't gray-950 and gray-500, or gray-800 and gray-700.
But naming things—and agreeing on naming—is hard. In the spirit of zero-config, I suggest taking Radix Color's backgrounds, borders, solids & fills paradigm. Or this palette semantics.
And once you've set this in tailwind.config, Typescript will jog your memory at your fingertips with autocomplete.
If you're extending a Tailwind theme and not writing your own, don't use a scale key that's already been used. You may inadvertently overwrite a class that you need to use.
You'll note in the previous colour config example that I set the --color-gray-base var to canvas, not base. If I used base then using this color scale as a text colour (text-base) would clash with the default font-size base value, which is also text-base.
This isn't a downfall of customising the Tailwind config, it's a legacy of its theme naming: setting font-size or color classes in Tailwind both use text-*.1
We can also use CSS variables to set spacings.
:root { --height-nav: 80px; --height-tab: 54px; --space-inset: 20px; --container-text-px: 660px; --container-hero-px: 1000px; }
spacing: { em: "1em", /* relate icon size to parent font-size */ nav: "var(--height-nav)", inset: "var(--space-inset)", text: "var(--container-text)", hero: "var(--container-hero)", }
One could argue this is over-engineering. Except that when it comes time to compute complex interactive layouts like sticky headers, scroll margins and so on, this upfront configuration work makes it straight forward and error-free, to the pixel.
<div className="top-[calc(theme(spacing.nav)+theme(spacing.tab))]"> <div className="scroll-mt-[calc(theme(spacing.nav)+theme(spacing.tab))]"> /* ... */ </div> </div>
Note again the use of semantic naming makes it easy to remember and use.
We have now configured typography, colour and spacing tokens in a manner that is easy to understand and maintain in a single, centralised place. And we don't need to wrire as many classes to implement the system. Winning. And there's further steps we can take to reduce this implementation overhead.
What if I told you there's a way to completely avoid writing text-lg lg:text-xl xl:text-2xl p-2 md:p-4 lg:p-8 everywhere?
We can avoid setting responsive font-size classes by using clamp as a a font-size value in tailwind.config. Here's the simple clamp function I use.
fontSize: { title: [ /* clamp(17px, 14.1429px + 0.5714vw, 21px) */ generateClampSize(500, 1200, 17, 21), { lineHeight: 1.5, letterSpacing: "-0.015em" }, ]; }
So instead of writing text-lg lg:text-xl xl:text-2xl we can just write text-title. Once again, by hoisting font-size responsiveness into a clamp value, we avoid the "implement classes" pitfall again, saving mental effort, errors and debugging time.
Keep in mind, this means we've moved from text-lg lg:text-xl xl:text-2xl leading-none tracking-wide to text-title by properly configuring Tailwind. Winning!
/* 17px at 500px, 21px at 1200, fluidly calculated inbetween */ /* …with default line-height and letter-spacing also specified */ <h2 className="text-title"> Heading copy </h2>
We can also do this for spacing. When extending a theme, I prefix these keys with d for "dynamic" to differentiate it from the default spacing scale.
spacing: { /* lower value is 2/3 of upper value */ d4: generateClampSize(500, 1200, 10.5, 16), d8: generateClampSize(500, 1200, 21, 32), d16: generateClampSize(500, 1200, 43, 64), d24: generateClampSize(500, 1200, 64, 96), d64: generateClampSize(500, 1200, 171, 256), }
This allows us to write py-d24 instead of py-16 md:py-20 lg:py-24. This alleviates the weight of holding a range of website versions for each media-query in our minds. Instead it encourages us to picture fluidly responsive layouts where measurements don't matter as much as consistent relationships.
<main className="pt-d24 pb-d64 space-y-w8"> <header className="container max-w-hero space-y-1"> /* ... */ </header> <article className="container space-y-2"> /* ... */ </article> </main>
Well-crafted UI is your last defense against the coming slopwave of careless AI apps. Here's how customizing Tailwind can save you time and headaches so you can focus on the irrational amount of care it takes to build UI that works in the blink of an eye:
Yes, there's an upfront time cost. But it pays off in spades: less code, fewer errors, greater design consistency, and a team that actually understands the system.
Next up: We'll explore how to use Class Variance Authority to create a bulletproof styling API with semantic props drawn from Tailwind. Stay tuned.
This is also why I dislike using tailwind-merge to remove duplicate Tailwind classes in JSX. More often than not, I find it removing a text-color in favour of a text-fontSize when both are needed. I'm surprised more developers don't raise this issue. ↩
以上是將 Tailwind 配置為設計系統的詳細內容。更多資訊請關注PHP中文網其他相關文章!