מעצבים קומפוננטות React עם CSS רגיל. תנו לאלמנטים class עם ה-prop className וכתבו את הכללים בגיליון סגנונות, השתמשו ב-prop style לערכים שמגיעים מ-JavaScript, ופנו ל-CSS Modules או ל-Tailwind כשהפרויקט גדל. הדוגמה שמתחת מגדירה את ה-CSS שלה באלמנט <style> כדי שתוכל לרוץ בדף הזה; בפרויקט הוא היה נמצא בקובץ .css.
שנו את #2563eb ל-#16a34a בכלל .btn והכפתור יהפוך לירוק. כל מה שאתם יודעים על CSS חל כאן: סלקטורים, media queries, מצבי hover, אנימציות.
className, לא class
JSX מתקמפל ל-JavaScript, שבה class היא מילה שמורה, ולכן React משתמשת ב-className, השם שה-DOM עצמו משתמש בו למאפיין הזה (element.className). הדפדפן עדיין מקבל מאפיין class רגיל. אותו שינוי שם חל על htmlFor במקום for בתוויות. React 19 מקבלת class עם אזהרה בפיתוח, אבל className הוא השם הנכון וזה שכל בסיסי הקוד משתמשים בו.
className מקבל מחרוזת, ולכן כמה classes מופרדים ברווחים: className="btn btn-primary". מכיוון שהערך הוא JavaScript, אפשר לחשב אותו.
classes מותנים
כדי להוסיף class רק בחלק מהמקרים, בנו את המחרוזת עם טרנרי או template literal. סרגל טאבים שמדגיש את הטאב הנבחר הוא המקרה הקלאסי.
לחצו על Lessons והקו התחתון זז, כי רק המחרוזת של הכפתור הזה מכילה עכשיו active. Reviews מקבל את ה-class disabled ואת המאפיין disabled, ולכן הוא אפור ומתעלם מלחיצות. ה-template literal משאיר רווחים מיותרים כשתנאי הוא false, ודפדפנים מתעלמים מהם. כשלקומפוננטה יש הרבה classes מותנים, מערך מסודר יותר:
const classes = ['tab', isActive && 'active', isDisabled && 'disabled']
.filter(Boolean)
.join(' ');
פרויקטים רבים משתמשים בחבילה הזעירה clsx לאותה מטרה: clsx('tab', { active: isActive, disabled: isDisabled }).
ה-prop style
ה-prop style מקבל אובייקט JavaScript, לא מחרוזת. לכן יש בו סוגריים כפולים: הזוג החיצוני פותח ביטוי JavaScript, הזוג הפנימי הוא האובייקט עצמו. שמות המאפיינים ב-camelCase (backgroundColor, fontSize, borderRadius), והערכים הם מחרוזות או מספרים. מספר על מאפיין אורך הופך לפיקסלים, כך ש-padding: 12 פירושו 12px.
סגנונות inline מצטיינים כשערך מחושב בזמן הרינדור, כמו הרוחב של פס התקדמות.
לחצו על Add 10% עד שהפס מתמלא והוא הופך לירוק. שימו לב ל-width: percent + '%': מספר חשוף היה פירושו פיקסלים, ולכן יחידות שאינן px נכתבות במחרוזת. מאפיינים בלי יחידה נשארים מספרים רגילים, כמו opacity: 0.8, zIndex: 2, flexGrow: 1 ו-lineHeight: 1.5 (שהוא מכפיל, לא פיקסלים).
לסגנונות inline יש מגבלות. הם לא יכולים לבטא :hover, :focus, media queries או אנימציות keyframe, וכל אלמנט נושא עותק משלו של הכללים. השתמשו בהם לערכים הדינמיים ושמרו את השאר ב-CSS.
משתני CSS מתוך props
דרך ביניים טובה היא לקבוע custom property של CSS ב-style ולהשתמש בו מגיליון הסגנונות. הכללים נשארים ב-CSS, כולל מצבי hover, ורק הערך מגיע מ-JavaScript.
בחרו צבע חדש ושני הכפתורים עוקבים אחריו, ומילוי ה-hover עדיין עובד כי הוא CSS רגיל. שמות של custom properties שומרים על המקפים שלהם באובייקט style, ולכן הם נכתבים במירכאות: { '--accent': accent }.
קובצי CSS
בפרויקט אמיתי כותבים סגנונות בקובצי .css ומייבאים אותם מהקומפוננטה שצריכה אותם. עם Vite, ה-import מספיק: כלי ה-build רואה את import './Button.css', מוסיף את גיליון הסגנונות לדף בזמן פיתוח, ואורז אותו לקובץ CSS לפרודקשן. אין export לשימוש; ה-import קיים רק בשביל תופעת הלוואי שלו.
// Button.jsx
import './Button.css';
export default function Button({ children }) {
return <button className="button">{children}</button>;
}
/* Button.css */
.button {
background: #2563eb;
color: white;
border-radius: 6px;
}
המלכודת היא שכל גיליון סגנונות מיובא הוא גלובלי. כלל .button ב-Button.css מעצב כל אלמנט עם ה-class הזה, בכל מקום באפליקציה, ושני קבצים שמגדירים שניהם .title דורסים זה את זה. צוותים מתמודדים עם זה עם מוסכמות שמות כמו BEM (.card__title), או עם CSS Modules.
CSS Modules
קובץ שמסתיים ב-.module.css הוא CSS Module. ייבוא שלו נותן אובייקט שממפה כל שם class שכתבתם לשם ייחודי שנוצר אוטומטית, כך ש-class יכול לחול רק במקום שבו משתמשים בו. Vite ו-Next.js תומכים בזה בלי הגדרות.
// Card.jsx
import styles from './Card.module.css';
export default function Card({ title, children, featured }) {
return (
<section className={featured ? `${styles.card} ${styles.featured}` : styles.card}>
<h3 className={styles.title}>{title}</h3>
{children}
</section>
);
}
/* Card.module.css */
.card { border: 1px solid #ddd; border-radius: 10px; padding: 16px; }
.featured { border-color: #2563eb; }
.title { margin: 0; }
בדפדפן ה-class יוצא משהו כמו _card_1x2ab_1. קומפוננטה אחרת יכולה להגדיר .title משלה והשניים אף פעם לא מתנגשים. כותבים CSS רגיל, כולל כללי hover ו-media queries, ולכן CSS Modules הם ברירת מחדל נפוצה לסגנונות של קומפוננטות.
Tailwind CSS עם React
Tailwind נוקטת גישה אחרת: במקום לכתוב כללים, מרכיבים מחלקות utility קטנות ישירות ב-className. px-4 קובע padding אופקי, rounded-lg מעגל את הפינות, hover:bg-blue-700 משנה את הרקע ב-hover. Tailwind סורקת את הקבצים שלכם ומייצרת CSS רק עבור המחלקות שבהן משתמשים.
ההגדרה באפליקציית React של Vite (Tailwind v4) לוקחת שלושה צעדים:
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()],
});
/* src/index.css, imported from main.jsx */
@import "tailwindcss";
אחר כך מעצבים קומפוננטות עם מחלקות:
function UpgradeCard({ active }) {
return (
<div className="max-w-xs rounded-xl border border-gray-200 p-4">
<h3 className="mb-2 text-lg font-semibold">Pro plan</h3>
<button
className={`rounded-md px-4 py-2 text-white ${active ? 'bg-green-600' : 'bg-blue-600 hover:bg-blue-700'}`}
>
{active ? 'Active' : 'Upgrade'}
</button>
</div>
);
}
Tailwind לא זמינה בעורכים בדף הזה, ולכן היא מוצגת כקוד סטטי. classes מותנים עובדים בדיוק כמו בדוגמת הטאבים שלמעלה, אבל כתבו כל שם class במלואו: Tailwind מוצאת מחלקות על ידי סריקת טקסט המקור, כך ששם שמורכב בזמן ריצה כמו `bg-${color}-600` אף פעם לא נוצר.
מתי להשתמש בכל אחד
- קובצי CSS רגילים מתאימים לאפליקציות קטנות ולסגנונות גלובליים (resets, גופנים, layout של הדף). שימו לב להתנגשויות בשמות classes כשהאפליקציה גדלה.
- CSS Modules מתאימים לרוב הסגנונות של קומפוננטות: CSS רגיל, מבודד אוטומטית, בלי ספרייה נוספת.
- Tailwind מתאימה לצוותים שמעדיפים לעצב בתוך ה-markup ורוצים סקאלת עיצוב משותפת למרווחים ולצבעים.
- ה-prop
styleנועד לערכים שמחושבים בזמן הרינדור: רוחב, מיקום, צבע שהמשתמש בחר. שלבו אותו עם משתני CSS כדי לשמור את כללי ה-hover וה-media ב-CSS.
אפשר לשלב ביניהם. לאפליקציה טיפוסית יש גיליון סגנונות גלובלי אחד, CSS Modules או Tailwind לקומפוננטות, וכמה סגנונות inline לערכים דינמיים. ספריות CSS-in-JS כמו styled-components ו-Emotion הן אפשרות נוספת שתפגשו בבסיסי קוד ישנים. הדף על התקנת React מראה איך מתחילים פרויקט Vite שבו כל אלה עובדים.
שאלות נפוצות
למה React משתמשת ב-className במקום class?
JSX מתקמפל ל-JavaScript, ושם class היא מילה שמורה. React משתמשת בשם מאפיין ה-DOM, className, שזה גם השם של element.className ב-JavaScript רגיל. בסוף זה עדיין הופך למאפיין class רגיל ב-HTML.
איך כותבים סגנונות inline ב-React?
מעבירים אובייקט ל-prop style, עם שמות מאפיינים ב-camelCase: style={{ backgroundColor: 'teal', fontSize: 18 }}. הסוגריים החיצוניים פותחים ביטוי JavaScript והפנימיים הם האובייקט. מספרים הופכים לפיקסלים במאפיינים שמקבלים אורך.
איך מוסיפים class באופן מותנה ב-React?
בונים את המחרוזת ב-JavaScript: className={isActive ? 'tab active' : 'tab'} או template literal כמו className={`tab ${isActive ? 'active' : ''}`}. כשיש הרבה תנאים, פונקציית עזר קטנה כמו החבילה clsx שומרת על קריאות.
האם קובצי CSS ב-React גלובליים?
כן. import './App.css' מוסיף את גיליון הסגנונות לכל הדף, כך ששמות ה-class שלו יכולים להתנגש עם קבצים אחרים. CSS Modules (App.module.css) משנים כל class לשם ייחודי, כך שהוא חל רק במקום שבו מייבאים אותו.
איך משתמשים ב-Tailwind עם React?
בפרויקט Vite, התקינו את tailwindcss ואת @tailwindcss/vite, הוסיפו את הפלאגין ל-vite.config.js, ושימו @import "tailwindcss"; בראש קובץ ה-CSS הראשי. אחר כך כתבו מחלקות utility ב-className, כמו className="px-4 py-2 rounded bg-blue-600 text-white".