Tailwind CSS چیست؟ آموزش کامل تیلویند با React و پروژه هوش مصنوعی
در این آموزش Tailwind CSS را از صفر یاد میگیرید؛ از نصب با Vite و Utility Classها تا طراحی Responsive، Dark Mode و Theme اختصاصی. در پایان یک اپلیکیشن هوش مصنوعی واقعی با React، Tailwind و API درواره میسازیم.
Tailwind CSS یکی از ابزارهای محبوب طراحی رابط کاربری وب است که بهجای ارائه Componentهای آماده، مجموعهای از Utility Classهای کوچک و قابلترکیب در اختیار توسعهدهنده قرار میدهد.
در CSS سنتی معمولاً برای هر بخش رابط یک Class اختصاصی تعریف میکنیم:
.send-button {
padding: 12px 20px;
color: white;
background-color: #6d4aff;
border-radius: 12px;
}
در Tailwind همان ظاهر با ترکیب Utility Classها مستقیماً روی عنصر ساخته میشود:
<button
className="
rounded-xl
bg-violet-600
px-5
py-3
text-white
hover:bg-violet-700
"
>
ارسال پیام
</button>
این رویکرد در ابتدا ممکن است شلوغ به نظر برسد، اما در پروژههای Component-based مانند React، Vue و Next.js میتواند سرعت توسعه و یکپارچگی طراحی را افزایش دهد.
در این مقاله Tailwind را فقط در حد معرفی چند Class بررسی نمیکنیم. ابتدا منطق Utility-first، نصب جدید با Vite، Responsive Design، Dark Mode، Theme Variables و الگوهای نگهداری کد را یاد میگیریم. سپس یک اپلیکیشن هوش مصنوعی فارسی با React، Tailwind CSS و API درواره میسازیم.
Tailwind CSS چیست؟
Tailwind CSS یک فریمورک CSS مبتنی بر Utility Class است. هر Utility معمولاً یک یا چند ویژگی محدود CSS را اعمال میکند.
مثال:
<div class="p-6">
محتوا
</div>
Class برابر با p-6 Padding را تنظیم میکند.
<div class="rounded-2xl">
محتوا
</div>
Class برابر با rounded-2xl گوشههای عنصر را گرد میکند.
<div class="bg-white text-slate-900">
محتوا
</div>
این Classها رنگ پسزمینه و متن را مشخص میکنند.
طبق مستندات رسمی Tailwind CSS، طراحی رابط با ترکیب Utility Classهای محدود و هدفمند انجام میشود و حالتهایی مانند Hover، Focus و Responsive نیز از طریق Variantها قابلتعریف هستند.
Utility-first CSS چیست؟
در روش سنتی ابتدا نامی برای Component انتخاب میکنیم و سپس قوانین CSS را در فایل جداگانه مینویسیم:
<article class="model-card">
...
</article>
.model-card {
padding: 24px;
background-color: white;
border: 1px solid #e2e8f0;
border-radius: 16px;
}
در روش Utility-first، ویژگیها مستقیماً ترکیب میشوند:
<article
class="
rounded-2xl
border
border-slate-200
bg-white
p-6
"
>
...
</article>
مزیت اصلی این است که هنگام خواندن Markup میتوانید بخش زیادی از ظاهر عنصر را مشاهده کنید و برای هر تغییر کوچک مجبور نیستید میان فایل JSX و CSS جابهجا شوید.
آیا Tailwind جای CSS را میگیرد؟
خیر. Tailwind جای CSS را حذف نمیکند. Utility Classهای Tailwind در نهایت به CSS تبدیل میشوند.
برای استفاده حرفهای از Tailwind همچنان باید مفاهیم زیر را بدانید:
- Cascade
- Specificity
- Box Model
- Flexbox
- CSS Grid
- Position
- Overflow
- Responsive Design
- Typography
- رنگ و Contrast
- دسترسپذیری
- RTL و Logical Properties
اگر ندانید flex-1، min-w-0، overflow-auto یا grid-cols-3 در CSS چه کاری انجام میدهند، Debug کردن رابط Tailwind دشوار خواهد شد.
مزایای Tailwind CSS
سرعت ساخت رابط
برای تغییر فاصله، رنگ، Border یا Layout لازم نیست برای هر مورد Class اختصاصی بسازید.
یکپارچگی طراحی
Tailwind از مقیاسهای تعریفشده برای رنگ، فاصله، فونت، Radius و Breakpoint استفاده میکند. این موضوع احتمال استفاده از مقدارهای تصادفی را کاهش میدهد.
مناسب برای Component
در React، مجموعه Utilityها همراه Component نگهداری میشود:
function Badge({ children }) {
return (
<span
className="
inline-flex
items-center
rounded-full
bg-violet-50
px-3
py-1
text-xs
font-semibold
text-violet-700
"
>
{children}
</span>
);
}
ساخت Variant
حالتهای Hover، Focus، Disabled، Responsive و Dark Mode با Prefix ساخته میشوند:
<button
class="
bg-violet-600
hover:bg-violet-700
focus-visible:ring-4
disabled:cursor-not-allowed
disabled:opacity-50
dark:bg-violet-500
"
>
ارسال
</button>
CSS خروجی هدفمند
Tailwind Classهایی را که در Source پیدا میکند وارد خروجی میکند. بنابراین لازم نیست کل مجموعه Utilityها به کاربر نهایی ارسال شود.
محدودیتهای Tailwind CSS
طولانیشدن className
<button
className="
inline-flex
min-h-11
items-center
justify-center
gap-2
rounded-xl
bg-violet-600
px-5
py-2.5
text-sm
font-semibold
text-white
shadow-sm
transition
hover:bg-violet-700
focus-visible:outline-none
focus-visible:ring-4
focus-visible:ring-violet-200
disabled:cursor-not-allowed
disabled:opacity-50
"
>
ارسال
</button>
در Componentهای پیچیده باید Classها را منظم و مسئولیت Component را محدود نگه دارید.
نیاز به دانش CSS
Tailwind مشکلات CSS را حذف نمیکند. فقط روش نوشتن آن را تغییر میدهد.
وابستگی به فرایند Build
برای تجربه کامل Tailwind معمولاً به ابزار Build نیاز دارید.
Classهای شرطی پیچیده
با افزایش Variantها، مدیریت رشته Class ممکن است دشوار شود. در این حالت میتوان از تابع کمکی یا کتابخانه مدیریت Class استفاده کرد.
وابستگی ظاهری به Markup
تغییر طراحی معمولاً در فایل Component انجام میشود. برای بعضی تیمها این مزیت و برای برخی دیگر محدودیت محسوب میشود.
تفاوت Tailwind و Bootstrap
| ویژگی | Tailwind CSS | Bootstrap |
|---|---|---|
| رویکرد | Utility-first | Component-first |
| ظاهر پیشفرض | حداقلی و قابلشخصیسازی | Componentهای آماده |
| سرعت Prototype | بالا | بسیار بالا |
| کنترل طراحی | زیاد | نیازمند Override در طراحی خاص |
| JavaScript داخلی | ندارد | برای بعضی Componentها دارد |
| مناسب Design System اختصاصی | بسیار مناسب | امکانپذیر با شخصیسازی |
| Classهای HTML | Utilityهای متعدد | Classهای Component |
اگر میخواهید سریع یک رابط با Componentهای آماده بسازید، Bootstrap میتواند مناسب باشد. اگر Design System و ظاهر اختصاصی میخواهید، Tailwind انعطاف بیشتری ارائه میدهد.
تفاوت Tailwind و CSS Modules
CSS Modules و Tailwind رقیب مطلق یکدیگر نیستند.
CSS Module:
import styles from "./Button.module.css";
<button className={styles.primaryButton}>
ارسال
</button>
Tailwind:
<button
className="
rounded-xl
bg-violet-600
px-5
py-3
text-white
"
>
ارسال
</button>
در یک پروژه میتوان برای بیشتر طراحی از Tailwind و برای بعضی Styleهای پیچیده از CSS معمولی یا CSS Module استفاده کرد.
پیشنیازهای نصب Tailwind
برای پروژه این مقاله به موارد زیر نیاز دارید:
- Node.js
- npm
- React
- Vite
- ویرایشگر کد
- آشنایی مقدماتی با CSS و JavaScript
بررسی نصب Node.js و npm:
node --version
npm --version
ساخت پروژه React با Vite
دستور زیر را اجرا کنید:
npm create vite@latest tailwind-ai-dashboard -- --template react
وارد پوشه شوید:
cd tailwind-ai-dashboard
وابستگیهای اولیه را نصب کنید:
npm install
نصب Tailwind CSS با Vite
براساس روش رسمی نصب Tailwind با Vite، بستههای زیر را نصب کنید:
npm install tailwindcss @tailwindcss/vite
فایل vite.config.js:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [
react(),
tailwindcss()
],
server: {
proxy: {
"/api": {
target: "http://localhost:3001",
changeOrigin: true
}
}
}
});
در فایل src/index.css:
@import "tailwindcss";
پروژه را اجرا کنید:
npm run dev
اکنون میتوانید Utility Classهای Tailwind را در JSX استفاده کنید.
اولین Component با Tailwind
export function WelcomeCard() {
return (
<section
className="
mx-auto
max-w-xl
rounded-2xl
border
border-slate-200
bg-white
p-6
shadow-sm
"
>
<p
className="
text-sm
font-semibold
text-violet-600
"
>
آموزش Tailwind
</p>
<h1
className="
mt-2
text-2xl
font-black
text-slate-900
"
>
اولین Component شما آماده است
</h1>
<p
className="
mt-3
leading-8
text-slate-600
"
>
این کارت بدون تعریف Class اختصاصی ساخته شده است.
</p>
</section>
);
}
ساختار نام Utilityها
بیشتر Utilityها ساختار قابلپیشبینی دارند:
ویژگی - مقدار
مثال:
p-4
mt-6
text-lg
bg-white
rounded-xl
grid-cols-3
گاهی Prefix حالت نیز اضافه میشود:
hover:bg-violet-700
md:grid-cols-2
dark:bg-slate-900
focus-visible:ring-4
ساختار مفهومی:
Variant : Utility
فاصلهگذاری در Tailwind
Padding
<div class="p-4">Padding همه جهات</div>
<div class="px-6">Padding افقی</div>
<div class="py-3">Padding عمودی</div>
<div class="pt-4">Padding بالا</div>
<div class="pb-4">Padding پایین</div>
Margin
<div class="m-4">Margin همه جهات</div>
<div class="mx-auto">مرکز افقی</div>
<div class="mt-6">فاصله بالا</div>
<div class="mb-4">فاصله پایین</div>
Gap
برای فاصله میان فرزندان Flex و Grid:
<div class="flex gap-4">
...
</div>
در بسیاری از Layoutها استفاده از gap از Margin جداگانه برای هر فرزند سادهتر است.
اندازه در Tailwind
<div class="w-full">عرض کامل</div>
<div class="max-w-3xl">حداکثر عرض</div>
<div class="min-h-screen">حداقل ارتفاع صفحه</div>
<div class="size-12">عرض و ارتفاع برابر</div>
برای جلوگیری از خروج محتوای طولانی در Flex یا Grid:
<div class="min-w-0">
...
</div>
min-w-0 در رابطهایی مانند چت، جدول و داشبورد بسیار مهم است.
رنگ در Tailwind
<div class="bg-slate-950 text-white">
محتوای تیره
</div>
Border:
<div class="border border-slate-200">
...
</div>
Opacity رنگ:
<div class="bg-violet-600/10">
...
</div>
رنگ متن:
<p class="text-slate-600">
توضیحات
</p>
در پروژه حرفهای بهتر است رنگهای برند را به Theme اضافه کنید و از رنگهای تصادفی متعدد اجتناب شود.
Typography
<h1 class="text-3xl font-black tracking-tight">
عنوان اصلی
</h1>
<p class="text-base leading-8 text-slate-600">
متن توضیحات
</p>
Classهای رایج:
| Class | کاربرد |
|---|---|
text-sm | متن کوچک |
text-base | متن پایه |
text-xl | متن بزرگ |
font-medium | وزن متوسط |
font-bold | متن Bold |
leading-8 | ارتفاع خط |
tracking-tight | فاصله کمتر حروف |
text-center | تراز وسط |
truncate | کوتاهکردن متن تکخطی |
برای متن فارسی، leading-7 یا leading-8 معمولاً خوانایی بهتری نسبت به Line Height بسیار فشرده دارد.
Border، Radius و Shadow
<div
class="
rounded-2xl
border
border-slate-200
shadow-sm
"
>
...
</div>
نمونه دکمه:
<button
class="
rounded-xl
bg-violet-600
px-4
py-2
text-white
shadow-lg
shadow-violet-600/20
"
>
ادامه
</button>
Shadow زیاد در تمام Componentها رابط را شلوغ میکند. بهتر است سلسلهمراتب بصری مشخصی داشته باشید.
Flexbox در Tailwind
<div
class="
flex
items-center
justify-between
gap-4
"
>
<span>عنوان</span>
<button>عملیات</button>
</div>
Classهای پرکاربرد:
| Tailwind | CSS تقریبی |
|---|---|
flex | display: flex |
flex-col | flex-direction: column |
items-center | align-items: center |
justify-between | justify-content: space-between |
flex-1 | رشد عنصر |
shrink-0 | جلوگیری از کوچکشدن |
flex-wrap | انتقال به خط بعد |
gap-4 | فاصله میان آیتمها |
CSS Grid در Tailwind
<div
class="
grid
grid-cols-1
gap-5
md:grid-cols-2
xl:grid-cols-3
"
>
...
</div>
برای Sidebar و محتوای اصلی:
<div
class="
grid
min-h-screen
grid-cols-1
lg:grid-cols-[280px_minmax(0,1fr)]
"
>
...
</div>
مقدار داخل [] یک Arbitrary Value است و برای Layout اختصاصی استفاده میشود.
Arbitrary Value چیست؟
اگر مقدار موردنیاز در مقیاس پیشفرض وجود ندارد، میتوان از مقدار دلخواه استفاده کرد:
<div class="max-w-[920px]">
...
</div>
رنگ اختصاصی:
<div class="bg-[#6d4aff]">
...
</div>
Grid اختصاصی:
<div class="grid-cols-[260px_1fr]">
...
</div>
Arbitrary Value مفید است، اما استفاده زیاد از مقدارهای تصادفی میتواند Design System را از بین ببرد. مقدارهای پرتکرار را به Theme تبدیل کنید.
Responsive Design در Tailwind
Tailwind از رویکرد Mobile-first استفاده میکند. Utility بدون Prefix برای همه اندازهها اعمال میشود و Prefixهایی مانند md: یا lg: از Breakpoint مشخص به بالا فعال میشوند.
<div
class="
grid
grid-cols-1
gap-4
md:grid-cols-2
xl:grid-cols-4
"
>
...
</div>
یعنی:
- موبایل: یک ستون
- صفحه متوسط: دو ستون
- صفحه بزرگ: چهار ستون
در مستندات Responsive Design تیلویند، Variantهای Breakpoint و رویکرد Mobile-first توضیح داده شدهاند.
اشتباه رایج در Mobile-first
اگر میخواهید عنصر فقط در موبایل دیده شود:
<div class="block md:hidden">
منوی موبایل
</div>
اگر میخواهید فقط از md به بالا دیده شود:
<div class="hidden md:block">
منوی دسکتاپ
</div>
Hover، Focus و Disabled
<button
class="
rounded-xl
bg-violet-600
px-5
py-3
text-white
transition
hover:bg-violet-700
focus-visible:outline-none
focus-visible:ring-4
focus-visible:ring-violet-200
disabled:cursor-not-allowed
disabled:opacity-50
"
>
ارسال
</button>
برای دسترسپذیری، Focus قابلمشاهده را حذف نکنید. Hover نیز نباید تنها راه انتقال اطلاعات ضروری باشد.
Group و Peer
Group
برای تغییر فرزند براساس وضعیت والد:
<a
class="
group
block
rounded-xl
border
border-slate-200
p-4
hover:border-violet-300
"
>
<h3
class="
font-bold
text-slate-900
group-hover:text-violet-700
"
>
مدل هوش مصنوعی
</h3>
</a>
Peer
برای تغییر عنصر براساس وضعیت همسایه:
<label>
<input
type="checkbox"
class="peer sr-only"
>
<span
class="
text-slate-500
peer-checked:text-violet-700
"
>
فعالسازی
</span>
</label>
RTL در Tailwind
جهت صفحه را در HTML مشخص کنید:
<html lang="fa" dir="rtl">
برای تراز متن:
<p class="text-right">
متن فارسی
</p>
برای بخشهای کد و Model ID:
<code
dir="ltr"
class="block text-left font-mono"
>
YOUR_MODEL_ID
</code>
تا حد امکان Layout را با Flex، Grid و gap بسازید تا وابستگی مستقیم به چپ و راست کمتر شود.
در موارد لازم میتوان از Utilityهای منطقی مانند ms-* و me-* استفاده کرد:
<span class="ms-2">
متن
</span>
ms به ابتدای محور Inline و me به انتهای آن اشاره میکند و با RTL سازگارتر است.
Theme اختصاصی در Tailwind
در نسخههای جدید Tailwind میتوان Theme Variableها را در CSS تعریف کرد.
فایل src/index.css:
@import "tailwindcss";
@theme {
--color-darvareh-50: #f4f1ff;
--color-darvareh-100: #ebe5ff;
--color-darvareh-500: #7b5cff;
--color-darvareh-600: #6d4aff;
--color-darvareh-700: #5636db;
--font-sans:
Vazirmatn,
Tahoma,
Arial,
sans-serif;
--radius-card: 1.25rem;
}
سپس از Utilityهای مرتبط استفاده کنید:
<button
class="
rounded-card
bg-darvareh-600
text-white
hover:bg-darvareh-700
"
>
شروع
</button>
Theme Variableها برای این موارد مناسباند:
- رنگ برند
- فونت
- Breakpoint
- Radius
- Shadow
- Spacing
- Animation
جزئیات بیشتر در مستندات Theme Variables تیلویند ارائه شده است.
Dark Mode در Tailwind
برای استفاده براساس تنظیم سیستم:
<div
class="
bg-white
text-slate-900
dark:bg-slate-950
dark:text-slate-100
"
>
...
</div>
برای کنترل دستی با Class، در CSS یک Variant تعریف کنید:
@import "tailwindcss";
@custom-variant dark (
&:where(.dark, .dark *)
);
سپس Class برابر با dark را روی عنصر HTML قرار دهید:
document.documentElement.classList.add("dark");
حذف حالت تیره:
document.documentElement.classList.remove("dark");
تغییر حالت:
document.documentElement.classList.toggle("dark");
راهنمای کامل این روش در مستندات Dark Mode تیلویند موجود است.
ذخیره Theme انتخابی کاربر
function applyTheme(theme) {
const isDark = theme === "dark";
document.documentElement.classList.toggle(
"dark",
isDark
);
localStorage.setItem("theme", theme);
}
const savedTheme =
localStorage.getItem("theme");
const systemPrefersDark =
window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
applyTheme(
savedTheme ??
(systemPrefersDark ? "dark" : "light")
);
Theme یک تنظیم عمومی است و ذخیره آن در Local Storage معمولاً اشکالی ندارد. اما API Key یا اطلاعات حساس را در Local Storage ذخیره نکنید.
پروژه عملی: داشبورد هوش مصنوعی با React و Tailwind
در این پروژه ابزاری برای پردازش متن میسازیم که کاربر میتواند:
- متن فارسی وارد کند.
- نوع عملیات را انتخاب کند.
- از مدل بخواهد متن را خلاصه کند.
- متن را بازنویسی کند.
- نکات کلیدی را استخراج کند.
- لحن متن را حرفهایتر کند.
- پاسخ را در رابط مشاهده کند.
- درخواست را متوقف کند.
- رابط را در موبایل استفاده کند.
- Theme روشن یا تیره را انتخاب کند.
API Key فقط در Backend نگهداری میشود.
ساختار پروژه
tailwind-ai-dashboard/
├── backend/
│ ├── .env
│ ├── package.json
│ └── server.js
├── src/
│ ├── components/
│ │ ├── ActionSelector.jsx
│ │ ├── AppHeader.jsx
│ │ ├── ResultPanel.jsx
│ │ └── TextForm.jsx
│ ├── services/
│ │ └── textApi.js
│ ├── App.jsx
│ ├── index.css
│ └── main.jsx
├── .gitignore
├── index.html
├── package.json
└── vite.config.js
تنظیم CSS پروژه
فایل src/index.css:
@import "tailwindcss";
@custom-variant dark (
&:where(.dark, .dark *)
);
@theme {
--color-darvareh-50: #f4f1ff;
--color-darvareh-100: #eae4ff;
--color-darvareh-500: #7b5cff;
--color-darvareh-600: #6d4aff;
--color-darvareh-700: #5536dc;
--font-sans:
Vazirmatn,
Tahoma,
Arial,
sans-serif;
}
html {
min-width: 320px;
min-height: 100%;
}
body {
min-width: 320px;
min-height: 100dvh;
margin: 0;
}
button,
textarea {
font: inherit;
}
ساخت Service درخواست
فایل src/services/textApi.js:
export async function processText({
text,
action,
signal
}) {
const response = await fetch("/api/process", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
text,
action
}),
signal
});
const contentType =
response.headers.get("content-type") || "";
if (!contentType.includes("application/json")) {
throw new Error(
"پاسخ سرور در قالب مورد انتظار نیست."
);
}
const data = await response.json();
if (!response.ok) {
throw new Error(
data.error ||
`خطای HTTP: ${response.status}`
);
}
if (
typeof data.result !== "string" ||
!data.result.trim()
) {
throw new Error(
"نتیجه معتبری دریافت نشد."
);
}
return data.result;
}
Component انتخاب عملیات
فایل src/components/ActionSelector.jsx:
const actions = [
{
id: "summarize",
title: "خلاصهسازی",
description: "تبدیل متن به خلاصهای دقیق"
},
{
id: "rewrite",
title: "بازنویسی",
description: "روانتر و حرفهایتر کردن متن"
},
{
id: "key_points",
title: "نکات کلیدی",
description: "استخراج مهمترین نکات متن"
},
{
id: "formal",
title: "لحن رسمی",
description: "تبدیل متن به نگارش رسمی"
}
];
export function ActionSelector({
value,
onChange,
disabled
}) {
return (
<fieldset disabled={disabled}>
<legend
className="
mb-3
text-sm
font-bold
text-slate-900
dark:text-white
"
>
نوع پردازش را انتخاب کنید
</legend>
<div
className="
grid
grid-cols-1
gap-3
sm:grid-cols-2
xl:grid-cols-4
"
>
{actions.map((action) => {
const isSelected =
value === action.id;
return (
<label
key={action.id}
className={`
cursor-pointer
rounded-2xl
border
p-4
transition
${
isSelected
? `
border-darvareh-500
bg-darvareh-50
ring-4
ring-darvareh-500/10
dark:bg-darvareh-500/10
`
: `
border-slate-200
bg-white
hover:border-darvareh-300
dark:border-slate-700
dark:bg-slate-900
`
}
`}
>
<input
className="sr-only"
type="radio"
name="action"
value={action.id}
checked={isSelected}
onChange={(event) => {
onChange(event.target.value);
}}
/>
<span
className="
block
font-bold
text-slate-900
dark:text-white
"
>
{action.title}
</span>
<span
className="
mt-1
block
text-xs
leading-6
text-slate-500
dark:text-slate-400
"
>
{action.description}
</span>
</label>
);
})}
</div>
</fieldset>
);
}
در این Component، Classهای محدود و مشخص بهصورت شرطی انتخاب میشوند. همه Classها به شکل کامل در Source وجود دارند و ابزار Build میتواند آنها را شناسایی کند.
چرا Class پویا را کامل مینویسیم؟
این الگو مناسب نیست:
const color = "violet";
<div className={`bg-${color}-600`}>
...
</div>
ابزار تولید CSS معمولاً Source را بهصورت متن بررسی میکند و ممکن است Class ساختهشده در Runtime را پیدا نکند.
نسخه مناسب:
const colorClasses = {
violet: "bg-violet-600",
blue: "bg-blue-600",
emerald: "bg-emerald-600"
};
<div className={colorClasses[color]}>
...
</div>
Classهای کامل و قابلشناسایی را در Source قرار دهید.
ساخت Header و Dark Mode
فایل src/components/AppHeader.jsx:
export function AppHeader({
isDark,
onToggleTheme
}) {
return (
<header
className="
flex
flex-col
gap-5
border-b
border-slate-200
px-5
py-5
dark:border-slate-800
sm:flex-row
sm:items-center
sm:justify-between
lg:px-8
"
>
<div>
<p
className="
text-xs
font-bold
text-darvareh-600
dark:text-darvareh-500
"
>
پروژه React و Tailwind CSS
</p>
<h1
className="
mt-1
text-xl
font-black
tracking-tight
text-slate-950
dark:text-white
sm:text-2xl
"
>
پردازش متن با هوش مصنوعی
</h1>
</div>
<div className="flex items-center gap-3">
<a
className="
rounded-xl
border
border-slate-200
px-4
py-2
text-sm
font-semibold
text-slate-700
transition
hover:border-darvareh-300
hover:text-darvareh-700
focus-visible:outline-none
focus-visible:ring-4
focus-visible:ring-darvareh-500/15
dark:border-slate-700
dark:text-slate-200
"
href="https://darvareh.ir/models"
target="_blank"
rel="noreferrer"
>
مدلها
</a>
<button
className="
inline-flex
size-10
items-center
justify-center
rounded-xl
bg-slate-100
text-lg
text-slate-700
transition
hover:bg-slate-200
focus-visible:outline-none
focus-visible:ring-4
focus-visible:ring-darvareh-500/15
dark:bg-slate-800
dark:text-slate-200
dark:hover:bg-slate-700
"
type="button"
onClick={onToggleTheme}
aria-label={
isDark
? "فعالکردن حالت روشن"
: "فعالکردن حالت تیره"
}
>
<span aria-hidden="true">
{isDark ? "☀" : "☾"}
</span>
</button>
</div>
</header>
);
}
ساخت فرم متن
فایل src/components/TextForm.jsx:
import {
useState
} from "react";
import {
ActionSelector
} from "./ActionSelector.jsx";
export function TextForm({
isLoading,
onSubmit,
onCancel
}) {
const [text, setText] = useState("");
const [action, setAction] =
useState("summarize");
const [error, setError] = useState("");
async function handleSubmit(event) {
event.preventDefault();
const normalizedText = text.trim();
if (!normalizedText) {
setError("لطفاً متن را وارد کنید.");
return;
}
if (normalizedText.length > 12000) {
setError(
"متن نباید بیشتر از ۱۲۰۰۰ کاراکتر باشد."
);
return;
}
setError("");
await onSubmit({
text: normalizedText,
action
});
}
return (
<form
className="
space-y-6
rounded-3xl
border
border-slate-200
bg-white
p-5
shadow-sm
dark:border-slate-800
dark:bg-slate-900
lg:p-7
"
onSubmit={handleSubmit}
>
<ActionSelector
value={action}
onChange={setAction}
disabled={isLoading}
/>
<div>
<div
className="
mb-2
flex
items-center
justify-between
gap-4
"
>
<label
className="
text-sm
font-bold
text-slate-900
dark:text-white
"
htmlFor="source-text"
>
متن ورودی
</label>
<span
className="
text-xs
text-slate-500
dark:text-slate-400
"
>
{text.length.toLocaleString("fa-IR")}
{" / "}
۱۲٬۰۰۰
</span>
</div>
<textarea
id="source-text"
className="
min-h-72
w-full
resize-y
rounded-2xl
border
border-slate-200
bg-slate-50
px-4
py-3
leading-8
text-slate-900
outline-none
transition
placeholder:text-slate-400
focus:border-darvareh-500
focus:bg-white
focus:ring-4
focus:ring-darvareh-500/10
disabled:cursor-not-allowed
disabled:opacity-60
dark:border-slate-700
dark:bg-slate-950
dark:text-white
dark:focus:border-darvareh-500
"
value={text}
maxLength="12000"
disabled={isLoading}
placeholder="متنی را که میخواهید پردازش شود وارد کنید..."
onChange={(event) => {
setText(event.target.value);
if (error) {
setError("");
}
}}
/>
</div>
{error && (
<p
className="
rounded-xl
border
border-red-200
bg-red-50
px-4
py-3
text-sm
text-red-700
dark:border-red-900
dark:bg-red-950/40
dark:text-red-300
"
role="alert"
>
{error}
</p>
)}
<div
className="
flex
flex-col-reverse
gap-3
sm:flex-row
sm:justify-end
"
>
{isLoading && (
<button
className="
min-h-11
rounded-xl
bg-slate-100
px-5
py-2.5
text-sm
font-bold
text-slate-700
transition
hover:bg-slate-200
focus-visible:outline-none
focus-visible:ring-4
focus-visible:ring-slate-200
dark:bg-slate-800
dark:text-slate-200
"
type="button"
onClick={onCancel}
>
توقف پردازش
</button>
)}
<button
className="
min-h-11
rounded-xl
bg-darvareh-600
px-6
py-2.5
text-sm
font-bold
text-white
shadow-lg
shadow-darvareh-600/20
transition
hover:-translate-y-0.5
hover:bg-darvareh-700
focus-visible:outline-none
focus-visible:ring-4
focus-visible:ring-darvareh-500/20
disabled:cursor-not-allowed
disabled:opacity-50
"
type="submit"
disabled={
isLoading || !text.trim()
}
>
{isLoading
? "در حال پردازش..."
: "پردازش با هوش مصنوعی"}
</button>
</div>
</form>
);
}
ساخت پنل نتیجه
فایل src/components/ResultPanel.jsx:
import {
useState
} from "react";
export function ResultPanel({
result,
error,
isLoading
}) {
const [copied, setCopied] = useState(false);
async function handleCopy() {
if (!result) {
return;
}
await navigator.clipboard.writeText(result);
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 1500);
}
return (
<section
className="
flex
min-h-[420px]
min-w-0
flex-col
rounded-3xl
border
border-slate-200
bg-white
p-5
shadow-sm
dark:border-slate-800
dark:bg-slate-900
lg:p-7
"
>
<div
className="
flex
items-center
justify-between
gap-4
"
>
<h2
className="
font-black
text-slate-950
dark:text-white
"
>
نتیجه
</h2>
<button
className="
rounded-xl
bg-slate-100
px-3
py-2
text-xs
font-bold
text-slate-700
hover:bg-slate-200
disabled:cursor-not-allowed
disabled:opacity-40
dark:bg-slate-800
dark:text-slate-200
"
type="button"
disabled={!result || isLoading}
onClick={handleCopy}
>
{copied ? "کپی شد" : "کپی نتیجه"}
</button>
</div>
<div
className="
mt-5
flex-1
overflow-auto
rounded-2xl
bg-slate-50
p-4
dark:bg-slate-950
"
>
{isLoading && (
<div
className="
flex
h-full
min-h-64
items-center
justify-center
"
>
<div className="text-center">
<span
className="
mx-auto
block
size-10
animate-spin
rounded-full
border-4
border-darvareh-100
border-t-darvareh-600
"
aria-hidden="true"
></span>
<p
className="
mt-4
text-sm
text-slate-500
dark:text-slate-400
"
>
مدل در حال پردازش متن است...
</p>
</div>
</div>
)}
{!isLoading && error && (
<p
className="
rounded-xl
border
border-red-200
bg-red-50
p-4
text-sm
leading-7
text-red-700
dark:border-red-900
dark:bg-red-950/40
dark:text-red-300
"
role="alert"
>
{error}
</p>
)}
{!isLoading && !error && result && (
<p
className="
whitespace-pre-wrap
break-words
leading-8
text-slate-700
dark:text-slate-200
"
>
{result}
</p>
)}
{!isLoading && !error && !result && (
<div
className="
flex
h-full
min-h-64
items-center
justify-center
text-center
"
>
<div>
<div
className="
mx-auto
grid
size-14
place-items-center
rounded-2xl
bg-darvareh-50
text-2xl
text-darvareh-600
dark:bg-darvareh-500/10
"
aria-hidden="true"
>
✦
</div>
<p
className="
mt-4
text-sm
leading-7
text-slate-500
dark:text-slate-400
"
>
متن را وارد و نوع پردازش را انتخاب کنید.
</p>
</div>
</div>
)}
</div>
</section>
);
}
ساخت App اصلی
فایل src/App.jsx:
import {
useEffect,
useRef,
useState
} from "react";
import {
AppHeader
} from "./components/AppHeader.jsx";
import {
ResultPanel
} from "./components/ResultPanel.jsx";
import {
TextForm
} from "./components/TextForm.jsx";
import {
processText
} from "./services/textApi.js";
function getInitialTheme() {
const savedTheme =
localStorage.getItem("theme");
if (savedTheme === "dark") {
return true;
}
if (savedTheme === "light") {
return false;
}
return window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
}
export default function App() {
const [isDark, setIsDark] =
useState(getInitialTheme);
const [result, setResult] = useState("");
const [error, setError] = useState("");
const [isLoading, setIsLoading] =
useState(false);
const controllerRef = useRef(null);
useEffect(() => {
document.documentElement.classList.toggle(
"dark",
isDark
);
localStorage.setItem(
"theme",
isDark ? "dark" : "light"
);
}, [isDark]);
async function handleProcess({
text,
action
}) {
setIsLoading(true);
setError("");
setResult("");
const controller =
new AbortController();
controllerRef.current = controller;
try {
const output = await processText({
text,
action,
signal: controller.signal
});
setResult(output);
} catch (requestError) {
if (requestError.name === "AbortError") {
setError("درخواست متوقف شد.");
return;
}
setError(
requestError.message ||
"خطای غیرمنتظره رخ داد."
);
} finally {
controllerRef.current = null;
setIsLoading(false);
}
}
function handleCancel() {
controllerRef.current?.abort();
}
return (
<div
className="
min-h-dvh
bg-slate-100
px-0
py-0
text-slate-900
transition-colors
dark:bg-slate-950
dark:text-white
sm:px-5
sm:py-5
lg:px-8
"
>
<main
className="
mx-auto
min-h-dvh
max-w-7xl
overflow-hidden
bg-slate-50
shadow-2xl
shadow-slate-900/5
dark:bg-slate-950
sm:min-h-[calc(100dvh-2.5rem)]
sm:rounded-3xl
sm:border
sm:border-slate-200
dark:sm:border-slate-800
"
>
<AppHeader
isDark={isDark}
onToggleTheme={() => {
setIsDark((current) => !current);
}}
/>
<div
className="
grid
grid-cols-1
gap-5
p-4
lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]
lg:p-8
"
>
<TextForm
isLoading={isLoading}
onSubmit={handleProcess}
onCancel={handleCancel}
/>
<ResultPanel
result={result}
error={error}
isLoading={isLoading}
/>
</div>
<p
className="
px-5
pb-6
text-center
text-xs
text-slate-500
dark:text-slate-400
"
>
خروجی هوش مصنوعی ممکن است نیازمند بررسی باشد.
</p>
</main>
</div>
);
}
ساخت Backend امن
پوشه Backend را ایجاد کنید:
mkdir backend
cd backend
npm init -y
npm install express dotenv
فایل backend/package.json:
{
"name": "tailwind-ai-backend",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js"
},
"dependencies": {
"dotenv": "^17.0.0",
"express": "^5.0.0"
}
}
نسخه واقعی وابستگیها ممکن است هنگام نصب متفاوت باشد. نسخه ثبتشده توسط npm در package.json و package-lock.json را مبنا قرار دهید.
فایل backend/server.js:
import "dotenv/config";
import express from "express";
const app = express();
const port = Number(
process.env.PORT || 3001
);
const apiKey =
process.env.DARVAREH_API_KEY;
const modelId =
process.env.DARVAREH_MODEL_ID;
const chatUrl =
"https://api.darvareh.ir/v1/chat/completions";
const actionInstructions = {
summarize:
"متن را دقیق و فشرده خلاصه کن. نکات مهم حذف نشوند.",
rewrite:
"متن را روان، منسجم و حرفهای بازنویسی کن. معنی اصلی تغییر نکند.",
key_points:
"مهمترین نکات متن را بهصورت فهرست روشن استخراج کن.",
formal:
"متن را با لحن رسمی و حرفهای بازنویسی کن. اطلاعات اصلی حفظ شوند."
};
app.disable("x-powered-by");
app.use(
express.json({
limit: "64kb"
})
);
function validateInput(text, action) {
return (
typeof text === "string" &&
text.trim().length > 0 &&
text.length <= 12000 &&
Object.hasOwn(
actionInstructions,
action
)
);
}
app.post(
"/api/process",
async (request, response) => {
if (!apiKey || !modelId) {
return response.status(500).json({
error:
"تنظیمات API در سرور کامل نیست."
});
}
const {
text,
action
} = request.body ?? {};
if (!validateInput(text, action)) {
return response.status(400).json({
error:
"متن یا نوع عملیات معتبر نیست."
});
}
const controller =
new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, 60000);
try {
const upstreamResponse = await fetch(
chatUrl,
{
method: "POST",
headers: {
"Authorization":
`Bearer ${apiKey}`,
"Content-Type":
"application/json"
},
body: JSON.stringify({
model: modelId,
messages: [
{
role: "system",
content:
"شما یک دستیار حرفهای پردازش متن فارسی هستید. " +
"اطلاعاتی به متن اضافه نکنید مگر اینکه کاربر صریحاً درخواست کند."
},
{
role: "user",
content:
`${actionInstructions[action]}\n\n` +
`متن ورودی:\n${text.trim()}`
}
],
temperature: 0.3,
max_tokens: 1600
}),
signal: controller.signal
}
);
const rawBody =
await upstreamResponse.text();
let data;
try {
data = JSON.parse(rawBody);
} catch {
return response.status(502).json({
error:
"پاسخ سرویس مدل قابل پردازش نبود."
});
}
if (!upstreamResponse.ok) {
console.error(
"Model request failed:",
upstreamResponse.status
);
return response.status(502).json({
error:
"سرویس مدل پاسخ موفقی ارسال نکرد."
});
}
const result =
data?.choices?.[0]?.message?.content;
if (
typeof result !== "string" ||
!result.trim()
) {
return response.status(502).json({
error:
"نتیجه معتبری از مدل دریافت نشد."
});
}
return response.json({
result
});
} catch (error) {
if (error.name === "AbortError") {
return response.status(504).json({
error:
"زمان انتظار برای پاسخ به پایان رسید."
});
}
console.error(
"Process request failed:",
error.message
);
return response.status(502).json({
error:
"ارتباط با سرویس مدل برقرار نشد."
});
} finally {
clearTimeout(timeoutId);
}
}
);
app.listen(port, () => {
console.log(
`Backend: http://localhost:${port}`
);
});
تنظیم متغیرهای محیطی
فایل backend/.env:
PORT=3001
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID
برای دریافت کلید API و شروع استفاده میتوانید در درواره ثبتنام کنید.
برای مشاهده مدلها، انتخاب Model ID و بررسی قیمت بهروز هر مدل نیز صفحه مدلهای درواره را ببینید.
کلید API را در فایل React، متغیر VITE_، Local Storage یا Repository عمومی قرار ندهید.
اجرای پروژه
Terminal اول:
cd backend
npm run dev
Terminal دوم در ریشه Frontend:
npm run dev
آدرس نمایشدادهشده توسط Vite را در مرورگر باز کنید.
مدیریت Classهای شرطی
در نمونه پروژه از Template Literal استفاده کردیم:
const className = `
rounded-xl
${
isActive
? "bg-violet-600 text-white"
: "bg-white text-slate-700"
}
`;
برای پروژه بزرگتر میتوانید یک تابع کوچک بسازید:
export function cn(...values) {
return values
.filter(Boolean)
.join(" ");
}
استفاده:
<button
className={cn(
"rounded-xl px-4 py-2",
isActive
? "bg-violet-600 text-white"
: "bg-slate-100 text-slate-700",
disabled && "cursor-not-allowed opacity-50"
)}
>
انتخاب
</button>
کتابخانههای مدیریت Class نیز وجود دارند، اما پیش از نصب وابستگی جدید بررسی کنید آیا پیچیدگی پروژه واقعاً به آن نیاز دارد.
چه زمانی Component بسازیم؟
هر مجموعه Class طولانی الزاماً نباید به Component تبدیل شود.
Component زمانی ارزشمند است که یکی از شرایط زیر وجود داشته باشد:
- در چند محل استفاده میشود.
- رفتار مشخص دارد.
- Variantهای مختلف دارد.
- تست مستقل لازم دارد.
- بخشی معنادار از رابط است.
- تغییر آن باید در یک نقطه انجام شود.
مثال Component دکمه:
export function Button({
variant = "primary",
className = "",
...props
}) {
const variants = {
primary:
"bg-darvareh-600 text-white hover:bg-darvareh-700",
secondary:
"bg-slate-100 text-slate-700 hover:bg-slate-200"
};
return (
<button
className={`
min-h-11
rounded-xl
px-5
py-2.5
text-sm
font-bold
transition
focus-visible:outline-none
focus-visible:ring-4
disabled:cursor-not-allowed
disabled:opacity-50
${variants[variant]}
${className}
`}
{...props}
/>
);
}
استفاده از @apply
Tailwind امکان ترکیب Utilityها در CSS با @apply را فراهم میکند:
.primary-button {
@apply rounded-xl bg-violet-600 px-5 py-3 text-white;
}
اما استفاده زیاد از @apply میتواند شما را دوباره به ساخت تعداد زیادی Class اختصاصی برگرداند و بخشی از مزیت Utility-first را کاهش دهد.
موارد قابلقبول:
- Style پایه عنصر مشترک
- Component در محیطی که Markup در کنترل شما نیست
- یکپارچهسازی با کتابخانه ثالث
- الگوی محدود و واقعاً پرتکرار
برای Componentهای React معمولاً استخراج Component از ساخت Class اختصاصی عمومیتر است.
دسترسپذیری در Tailwind
Tailwind بهصورت خودکار رابط را Accessible نمیکند. مسئولیت ساخت HTML معنایی و رفتار درست همچنان با توسعهدهنده است.
موارد مهم:
- از
buttonبرای عملیات استفاده کنید. - از
aبرای Navigation استفاده کنید. - Label فرم را حذف نکنید.
- Focus قابلمشاهده داشته باشید.
- Contrast رنگ را بررسی کنید.
- حالت Disabled واقعی تنظیم کنید.
- فقط به رنگ برای نمایش وضعیت وابسته نباشید.
- ترتیب Tab را بررسی کنید.
- برای Icon Button نام قابلدسترسی تعریف کنید.
- Loading را برای Screen Reader اعلام کنید.
نمونه Focus:
<button
class="
focus-visible:outline-none
focus-visible:ring-4
focus-visible:ring-violet-200
"
>
ارسال
</button>
Motion و ترجیح کاربر
برای محدودکردن Animation در صورت ترجیح کاربر:
<div
class="
motion-safe:animate-pulse
motion-reduce:animate-none
"
>
...
</div>
برای Transition:
<button
class="
transition
motion-reduce:transition-none
"
>
ادامه
</button>
خطاهای رایج Tailwind CSS
Utilityها اعمال نمیشوند
بررسی کنید:
- Tailwind نصب شده است؟
- Plugin به Vite اضافه شده؟
@import "tailwindcss";وجود دارد؟- فایل CSS در
main.jsxImport شده؟ - Dev Server پس از تغییر تنظیمات Restart شده؟
- نام Class صحیح است؟
- Class بهصورت پویا ساخته نشده؟
نصب قدیمی را با نصب جدید ترکیب کردهاید
بسیاری از آموزشهای قدیمی شامل فایلها و دستورهای متفاوت هستند. روش نصب را براساس مستندات نسخه فعلی Tailwind انتخاب کنید و دستورهای چند نسخه مختلف را با هم ترکیب نکنید.
Class پویا شناسایی نمیشود
نام کامل Classها را در Source قرار دهید:
const sizes = {
small: "text-sm",
large: "text-lg"
};
نه:
const className = `text-${size}`;
Responsive برعکس انتظار عمل میکند
Tailwind Mobile-first است:
<div class="text-sm md:text-base lg:text-lg">
...
</div>
Class پایه برای موبایل است و Prefixها از Breakpoint مربوط به بالا اعمال میشوند.
Dark Mode کار نمیکند
بررسی کنید:
- Variant مربوط به Dark Mode تعریف شده؟
- Class برابر با
darkروی عنصر درست قرار گرفته؟ - Utility دارای
dark:است؟ - Theme پیش از Render اولیه اعمال میشود؟
- Class توسط کد دیگری حذف نمیشود؟
Classها با هم تعارض دارند
مثال:
<div class="p-4 p-8">
...
</div>
از قراردادن Utilityهای متناقض اجتناب کنید. هنگام ترکیب Variantها باید مشخص باشد کدام مقدار در چه وضعیت اعمال میشود.
رابط روی موبایل Overflow دارد
Utilityهای زیر را بررسی کنید:
min-w-0
max-w-full
overflow-x-auto
break-words
whitespace-pre-wrap
در Grid از این الگو استفاده کنید:
<div class="grid grid-cols-[280px_minmax(0,1fr)]">
...
</div>
بهینهسازی و Build
ساخت نسخه Production:
npm run build
بررسی Build:
npm run preview
قبل از انتشار:
- حجم Bundle را بررسی کنید.
- صفحه موبایل را آزمایش کنید.
- Dark Mode را بررسی کنید.
- Classهای استفادهنشده و Componentهای قدیمی را حذف کنید.
- فونتها را بهینه کنید.
- تصویرها را فشرده کنید.
- Focus و Keyboard Navigation را بررسی کنید.
- خطای شبکه و Timeout را آزمایش کنید.
- Secretها را از Bundle بررسی کنید.
- فایل
.envرا در Git جستوجو کنید.
استفاده از هوش مصنوعی برای تولید Tailwind CSS
مدل هوش مصنوعی میتواند Componentهای Tailwind تولید کند، اما پرامپت باید دقیق باشد.
نمونه پرامپت:
یک Component داشبورد با React و Tailwind CSS بساز.
نیازمندیها:
- رابط فارسی و RTL باشد.
- از Utility Classهای نسخه فعلی Tailwind استفاده کن.
- طراحی Mobile-first باشد.
- در موبایل یک ستون و در Desktop دو ستون داشته باشد.
- Dark Mode را با dark: پشتیبانی کند.
- Focus قابلمشاهده برای Keyboard داشته باشد.
- از رنگهای تصادفی زیاد استفاده نکن.
- Classها را در Runtime با رشته ناقص نساز.
- از مقدار Arbitrary فقط در صورت نیاز استفاده کن.
- API Key در Frontend قرار نگیرد.
- JSX کامل و قابلاجرا ارائه کن.
خروجی مدل را از نظر این موارد بررسی کنید:
- سازگاری با نسخه نصبشده
- وجود Classهای منسوخ یا نامعتبر
- Responsive Design
- Dark Mode
- Contrast
- Focus
- RTL
- Overflow
- تکرار Utilityها
- HTML معنایی
- عدم وجود Secret
چکلیست Tailwind برای پروژه واقعی
- روش نصب با نسخه فعلی مستندات هماهنگ است.
- Plugin صحیح به Vite اضافه شده است.
- فایل CSS اصلی Import شده است.
- Theme برند تعریف شده است.
- رنگهای تصادفی محدود هستند.
- Arbitrary Value فقط در صورت نیاز استفاده شده است.
- Classها در Runtime بهصورت ناقص ساخته نمیشوند.
- طراحی Mobile-first است.
- RTL بررسی شده است.
- Dark Mode در تمام Componentهای اصلی تست شده است.
- Focus قابلمشاهده است.
- Contrast متن مناسب است.
- Loading، Error و Empty State طراحی شدهاند.
min-w-0در Layoutهای لازم وجود دارد.- متن طولانی از صفحه خارج نمیشود.
- Classهای پرتکرار به Component تبدیل شدهاند.
- از
@applyبیش از حد استفاده نشده است. - API Key در Frontend نیست.
- متغیرهای عمومی و Secret از هم جدا هستند.
- Build نهایی بدون خطا اجرا میشود.
نقشه راه یادگیری Tailwind
مرحله اول: CSS پایه
- Box Model
- Flexbox
- Grid
- Responsive Design
- Position
- Overflow
مرحله دوم: Utilityهای اصلی
- Spacing
- Sizing
- Color
- Typography
- Border
- Shadow
مرحله سوم: Layout
- Flex
- Grid
- Container
- Width
- Min Width
- Overflow
مرحله چهارم: Variantها
- Hover
- Focus
- Disabled
- Group
- Peer
- Responsive
مرحله پنجم: طراحی حرفهای
- Theme Variable
- Dark Mode
- RTL
- Accessibility
- Motion
- Design System
مرحله ششم: معماری Component
- Class شرطی
- Variant
- Component قابلاستفاده مجدد
- جلوگیری از تکرار
- تست رابط
سؤالهای متداول
Tailwind CSS چیست؟
Tailwind یک فریمورک CSS مبتنی بر Utility Class است که رابط با ترکیب Classهای کوچک مانند p-4، flex، bg-white و rounded-xl ساخته میشود.
آیا Tailwind جای CSS را میگیرد؟
خیر. Tailwind روی CSS ساخته شده است و برای استفاده صحیح از آن باید مفاهیم CSS را بدانید.
آیا Tailwind برای React مناسب است؟
بله. نگهداری Utilityها در کنار Component و ساخت Variantهای قابلاستفاده مجدد، Tailwind را به گزینهای مناسب برای React تبدیل میکند.
Tailwind بهتر است یا Bootstrap؟
Tailwind برای طراحی اختصاصی و Componentهای سفارشی انعطاف بیشتری دارد. Bootstrap برای ساخت سریع رابط با Componentهای آماده مناسب است. انتخاب به نیاز پروژه بستگی دارد.
آیا Tailwind باعث شلوغشدن JSX میشود؟
در Componentهای پیچیده ممکن است className طولانی شود. با تقسیم درست Component، مرتبکردن Utilityها و ساخت Variant میتوان این مشکل را کنترل کرد.
آیا میتوان CSS معمولی را کنار Tailwind استفاده کرد؟
بله. میتوانید در کنار Utilityها از CSS معمولی، CSS Module یا Styleهای اختصاصی استفاده کنید.
آیا Tailwind از Dark Mode پشتیبانی میکند؟
بله. با Variant برابر با dark: میتوان Styleهای حالت تیره را تعریف کرد. فعالشدن آن میتواند براساس تنظیم سیستم یا Class اختصاصی باشد.
آیا Tailwind از RTL پشتیبانی میکند؟
بله. جهت اصلی در HTML با dir="rtl" تنظیم میشود و Utilityهای منطقی مانند ms-* و me-* برای فاصله متناسب با جهت قابلاستفادهاند.
چرا Class پویا در Tailwind کار نمیکند؟
Tailwind باید نام کامل Class را در Source پیدا کند. رشتهای مانند bg-${color}-600 ممکن است قابلشناسایی نباشد. Classهای کامل را در Map قرار دهید.
آیا Tailwind برای سئو مناسب است؟
Tailwind مستقیماً رتبه سئو ایجاد نمیکند. خروجی درست HTML، سرعت، Responsive Design، دسترسپذیری و کیفیت محتوا عوامل مهمتری هستند. Tailwind میتواند در ساخت رابط سبک و واکنشگرا کمک کند.
آیا Tailwind برای پروژه بزرگ مناسب است؟
بله، بهشرط اینکه Theme، Component، Variant و قواعد طراحی مشخص باشند. استفاده بیقاعده از Utilityها میتواند رابط را ناسازگار کند.
آیا API Key را میتوان در پروژه React و Tailwind قرار داد؟
خیر. کد Frontend برای کاربران قابلمشاهده است. API Key باید در Backend یا Secret Manager نگهداری شود.
چگونه Tailwind را با Vite نصب کنیم؟
در روش فعلی، بستههای tailwindcss و @tailwindcss/vite نصب میشوند، Plugin به vite.config.js اضافه میشود و در CSS از @import "tailwindcss"; استفاده میکنیم.
آیا به فایل tailwind.config.js نیاز داریم؟
در روش جدید بسیاری از تنظیمات میتوانند مستقیماً در CSS و با Theme Variableها انجام شوند. نیاز دقیق به فایل Config به نسخه، Pluginها و ساختار پروژه بستگی دارد. راهنمای نسخه نصبشده را مبنا قرار دهید.
جمعبندی
Tailwind CSS روشی Utility-first برای طراحی رابط وب ارائه میدهد. بهجای ساخت Class اختصاصی برای هر عنصر، Utilityهای کوچک را ترکیب میکنیم و ظاهر Component را در کنار Markup نگه میداریم.
مهمترین مفاهیم Tailwind عبارتاند از:
- Utility Class
- Variant
- Responsive Design
- Mobile-first
- Flexbox و Grid
- Arbitrary Value
- Theme Variable
- Dark Mode
- Group و Peer
- Class شرطی
- Component قابلاستفاده مجدد
در پروژه عملی این مقاله، یک اپلیکیشن پردازش متن با React و Tailwind ساختیم که طراحی فارسی، Responsive و Dark Mode دارد. Frontend درخواست را به Backend ارسال میکند و Backend با استفاده از کلید محرمانه به API درواره متصل میشود.
این معماری مانع قرارگرفتن API Key در Bundle مرورگر میشود و امکان اعتبارسنجی ورودی، Timeout، کنترل خطا و انتخاب Model ID را در سمت سرور فراهم میکند.
برای دریافت کلید API و شروع توسعه محصول هوش مصنوعی میتوانید در درواره ثبتنام کنید. برای مشاهده مدلهای قابلاستفاده، Model ID و قیمتهای بهروز نیز صفحه مدلهای درواره را ببینید.
منابع تکمیلی
- نصب Tailwind CSS با Vite
- راهنمای Utility Classها
- طراحی Responsive در Tailwind
- Dark Mode در Tailwind
- Theme Variables در Tailwind
مقالات مرتبط
- ساخت چتبات هوش مصنوعی با Next.js، React و API درواره
- طراحی UI/UX با هوش مصنوعی؛ آموزش رابط کاربری از ایده تا کد
- ساخت سایت با هوش مصنوعی؛ معرفی ابزارها و آموزش کامل
- هوش مصنوعی با Node.js؛ ساخت اپلیکیشن با Express و API درواره
- ساخت اپلیکیشن هوش مصنوعی با Angular و TypeScript
- ساخت وباپ هوش مصنوعی با Vue.js و Nuxt
- ساخت اپلیکیشن هوش مصنوعی با SvelteKit و API درواره
- ساخت اپلیکیشن دسکتاپ هوش مصنوعی با Electron و JavaScript
- ساخت افزونه Chrome هوش مصنوعی با JavaScript
- آموزش ساخت چتبات با API هوش مصنوعی درواره
برای مطالعه شرایط استفاده و محدودیتهای مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.