Tailwind Setup and Utilities
Day 3 - ชั่วโมงที่ 1: เปลี่ยนจาก Custom CSS มาใช้ Tailwind
เป้าหมายของชั่วโมงนี้
หลังจบชั่วโมงนี้ ผู้เรียนควรสามารถ:
- อธิบายได้ว่า Tailwind CSS ช่วยเขียน Style อย่างไร
- ตรวจและเปิดใช้งาน Tailwind ใน Next.js Project ได้
- อ่าน Utility Class พื้นฐานได้โดยเชื่อมโยงกับ CSS ที่เคยเรียน
- แปลงโครงหน้าและ Form จาก Custom CSS เป็น Tailwind ทีละส่วนได้
- ลบ CSS เดิมเฉพาะส่วนที่ย้ายเสร็จแล้วได้อย่างปลอดภัย
ไฟล์ที่ใช้
package.json
postcss.config.mjs
app/globals.css
app/layout.tsx
app/page.tsx
components/IssueForm.tsxSlide 1: Recap จาก Day 2
หลังจบ Day 2 Project ของเรามี:
- Component
IssueForm,IssueListและStatusBadge - Type และ Mock Data ที่แยกเป็นไฟล์กลาง
- หน้า
/,/issues,/issues/newและ/issues/[id] - Navigation ที่ใช้ร่วมกันทุกหน้าผ่าน
app/layout.tsx - Custom CSS ทั้งหมดอยู่ใน
app/globals.css
Project ทำงานได้แล้ว แต่เมื่อ Component เพิ่มขึ้น CSS กลางจะยาวขึ้นและต้องสลับไฟล์ไปมาระหว่าง TSX กับ CSS
Hour นี้เราจะเริ่มย้าย Style มาไว้ใกล้ UI ด้วย Tailwind โดยยังไม่เปลี่ยนข้อมูลหรือการทำงานของระบบ
Slide 2: จาก Custom CSS ไปสู่ Tailwind
ตัวอย่างเพื่อทำความเข้าใจ: ยังไม่ต้องแก้ Project ใน Slide นี้
ก่อนหน้านี้เราเขียน Style โดยตั้งชื่อ Class แล้วประกาศ CSS แยกอีกไฟล์:
<button className="primary-button">ส่งข้อมูล</button>.primary-button {
padding: 12px 16px;
background: #0f766e;
color: white;
}Tailwind ให้เราใช้ Class สำเร็จรูปที่แทน CSS Property โดยตรง:
<button className="bg-teal-700 px-4 py-3 text-white">
ส่งข้อมูล
</button>ผลลัพธ์ใกล้เคียงเดิม แต่เห็น Style อยู่ข้าง Element ที่ใช้งานจริง
Slide 3: Tailwind CSS คืออะไร
ตัวอย่างเพื่อทำความเข้าใจ: ยังไม่ต้องแก้ Project ใน Slide นี้
Tailwind CSS คือ CSS Framework แบบ Utility-first คำว่า Utility หมายถึง Class ขนาดเล็กที่ทำหน้าที่อย่างใดอย่างหนึ่ง:
p-6ใช้กำหนดpaddingbg-whiteใช้กำหนดสีพื้นหลังtext-smใช้กำหนดขนาดตัวอักษรfont-boldใช้กำหนดน้ำหนักตัวอักษรrounded-lgใช้กำหนดความโค้งของมุมgridใช้กำหนดdisplay: grid
เรานำ Utility หลายตัวมาต่อกันเพื่อสร้าง Style ของ Element:
<section className="rounded-lg border border-slate-200 bg-white p-6">
...
</section>Tailwind ไม่ได้แทนที่ความรู้ CSS แต่เป็นอีกวิธีหนึ่งในการนำแนวคิด CSS มาใช้
Slide 4: ตรวจว่า Tailwind พร้อมใช้งาน
ลงมือทำ: เปิดไฟล์เพื่อตรวจเท่านั้น หากค่าตรงกับตัวอย่างไม่ต้องแก้ไข
Project นี้สร้างจาก create-next-app พร้อม Recommended Defaults จึงมี Tailwind มาให้แล้ว ไม่ต้องติดตั้งซ้ำ
ตรวจใน package.json ว่ามี:
"tailwindcss": "...",
"@tailwindcss/postcss": "..."ตรวจทั้งไฟล์ postcss.config.mjs:
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;@tailwindcss/postcss ทำหน้าที่ประมวลผล Tailwind Class ให้กลายเป็น CSS ที่ Browser ใช้งานได้
Slide 5: เปิดใช้งาน Tailwind อีกครั้ง
ลงมือแก้ Project: app/globals.css
Day 2 เราลบ Tailwind Import ออกชั่วคราวเพื่อใช้ Custom CSS เพียงชุดเดียว ตอนนี้ให้เพิ่มบรรทัดนี้ไว้ บนสุด ของ app/globals.css:
@import "tailwindcss";ใน app/layout.tsx ต้องยังมี Import ของ Global CSS:
import "./globals.css";Flow การทำงานคือ:
layout.tsx โหลด globals.css
↓
globals.css โหลด Tailwind
↓
Tailwind Class ใน TSX เริ่มทำงานหลังเปิด Tailwind หน้าตาบางส่วนอาจเปลี่ยนเล็กน้อยจาก Preflight ซึ่งเป็น Style พื้นฐานที่ช่วยให้ Browser แต่ละตัวเริ่มจากค่าที่ใกล้เคียงกัน
Slide 6: ใช้ Tailwind ตอนไหนและไฟล์ไหน
หลังเพิ่ม @import "tailwindcss"; แล้ว เราสามารถเริ่มใช้ Tailwind ได้ทันที โดยเขียน Utility Class ใน className ของ Element ภายในไฟล์ .tsx:
<h1 className="text-3xl font-bold text-slate-950">
ระบบแจ้งปัญหา IT
</h1>Hour นี้เราจะใช้ Tailwind ตามลำดับ:
| ไฟล์ | ส่วนที่จะปรับ |
|---|---|
app/layout.tsx | Navigation ที่ทุกหน้าใช้ร่วมกัน |
app/page.tsx | Header, Main และ Footer ของหน้า Home |
components/IssueForm.tsx | โครง Form, Field และ Button |
ไม่ต้อง Import Tailwind ในแต่ละไฟล์ TSX เพราะ app/layout.tsx โหลด globals.css ให้ทุกหน้าแล้ว ส่วน IssueList และ StatusBadge จะเริ่มใช้ Tailwind ใน Hour 2
Code <h1> ด้านบนเป็นเพียงตัวอย่าง เรายังไม่ต้องนำไปวางในไฟล์จริง โดยจะเริ่มแก้ UI ของ Project ตั้งแต่ Slide 13
Slide 7: globals.css ยังจำเป็น
แม้เริ่มใช้ Tailwind แล้ว globals.css ยังมีหน้าที่:
- Import Tailwind ด้วย
@import "tailwindcss"; - เก็บ Style ที่มีผลกับทั้งระบบ เช่น
body - เก็บ Custom CSS ของส่วนที่ยังไม่ได้ย้าย
หลักการย้าย Style ในชั่วโมงนี้:
- เพิ่ม Tailwind Class ให้ Element หนึ่งส่วน
- ตรวจหน้าเว็บว่าผลลัพธ์ถูกต้อง
- ลบ CSS เดิมของส่วนนั้น
- ทำซ้ำกับส่วนถัดไป
อย่าลบ CSS เดิมทั้งไฟล์ในครั้งเดียว เพราะ Component ที่ยังไม่ได้แปลงอาจเสีย Style
Slide 8: Utility สำหรับระยะห่าง
ตัวอย่างสำหรับฝึกอ่าน Class: ยังไม่ต้องแก้ Project
ตัวอักษรด้านหน้า Class บอกทิศทาง ส่วนตัวเลขบอกระดับของระยะห่าง:
<div className="p-4">Padding ทุกด้าน</div>
<div className="px-6 py-8">Padding แนวนอนและแนวตั้ง</div>
<section className="mt-6">Margin ด้านบน</section>
<div className="grid gap-6">ระยะห่างระหว่างลูก</div>pหมายถึง Padding ทุกด้านpxและpyหมายถึง Padding ตามแกน X และ Ymหมายถึง Margingapหมายถึงช่องว่างระหว่างลูกของ Flex หรือ Grid
ตัวเลขมากขึ้นหมายถึงระยะห่างมากขึ้น เช่น p-6 กว้างกว่า p-4
Slide 9: Utility สำหรับข้อความและกล่อง
ตัวอย่างสำหรับฝึกอ่าน Class: ยังไม่ต้องนำ Code ชุดนี้ไปวางใน Project
<section className="rounded-lg border border-slate-200 bg-white p-6">
<h2 className="text-xl font-bold text-slate-950">
รายการปัญหา
</h2>
<p className="mt-2 text-sm text-slate-600">
รายการปัญหาที่ถูกแจ้งเข้ามาในระบบ
</p>
</section>text-xlและtext-smกำหนดขนาดตัวอักษรfont-boldกำหนดน้ำหนักตัวอักษรtext-slate-*กำหนดสีข้อความbg-whiteกำหนดสีพื้นหลังborderและborder-slate-200กำหนดเส้นขอบrounded-lgทำให้มุมกล่องโค้ง
สีที่ลงท้ายด้วยตัวเลขน้อยจะอ่อนกว่า ส่วนตัวเลขมากจะเข้มกว่า เช่น slate-200 อ่อนกว่า slate-950
Slide 10: Utility สำหรับ Layout
ตัวอย่างสำหรับฝึกอ่าน Class: ยังไม่ต้องแก้ Project
จำกัดความกว้างและจัดกลาง
<main className="mx-auto max-w-5xl px-6 py-8">
...
</main>max-w-5xl ป้องกันเนื้อหากว้างเต็มจอบน Desktop ส่วน mx-auto จัดกล่องให้อยู่กลางหน้า
Flex
<div className="flex items-center justify-between gap-4">
...
</div>Flex เหมาะกับการจัดวาง Element ในแนวเดียว เช่น Navigation หรือ Header ที่มีข้อมูลฝั่งซ้ายและขวา ส่วน Grid จะใช้ใน Slide ถัดไปเพื่อจัด Layout หลายคอลัมน์
Slide 11: Responsive Prefix
ตัวอย่างสำหรับฝึกอ่าน Class: ยังไม่ต้องแก้ Project
Tailwind ใช้แนวคิด Mobile-first:
- Class ที่ไม่มี Prefix ทำงานกับทุกขนาดจอ
sm:เริ่มทำงานตั้งแต่จอเล็กขึ้นไปmd:เริ่มทำงานตั้งแต่จอกลางขึ้นไปlg:เริ่มทำงานตั้งแต่จอใหญ่ขึ้นไป
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
...
</div>อ่านตามลำดับ:
grid-cols-1 → ค่าเริ่มต้นเรียง 1 คอลัมน์
md:grid-cols-2 → เมื่อจอกว้างถึง md เปลี่ยนเป็น 2 คอลัมน์จอเล็กจึงอ่านง่ายก่อน แล้วค่อยเพิ่มจำนวนคอลัมน์เมื่อมีพื้นที่
Slide 12: State Prefix
ตัวอย่างสำหรับฝึกอ่าน Class: ยังไม่ต้องแก้ Project
State Prefix ทำให้ Style ทำงานเฉพาะตอน Element อยู่ในสถานะหนึ่ง:
<button className="bg-teal-700 hover:bg-teal-800">
ส่งข้อมูล
</button><input className="border focus:border-teal-700 focus:outline-none focus:ring-4 focus:ring-teal-100" />hover:ทำงานเมื่อผู้ใช้ชี้เมาส์focus:ทำงานเมื่อผู้ใช้คลิกหรือกด Tab มาที่ Elementdisabled:ทำงานเมื่อ Element ถูกปิดการใช้งาน
State ไม่ได้มีไว้เพื่อตกแต่งอย่างเดียว แต่ช่วยบอกผู้ใช้ว่าตอนนี้กำลังโต้ตอบกับ Element ใด
Slide 13: แปลง Navigation เป็น Tailwind
แก้บางส่วน: app/layout.tsx
แทน <nav>...</nav> เดิมด้วย:
<nav className="border-b border-slate-200 bg-white" aria-label="เมนูหลัก">
<div className="mx-auto flex max-w-5xl flex-wrap gap-2 px-6 py-3">
<Link className="rounded-md px-3 py-2 font-semibold text-slate-700 hover:bg-slate-100" href="/">
หน้าแรก
</Link>
<Link className="rounded-md px-3 py-2 font-semibold text-slate-700 hover:bg-slate-100" href="/issues">
รายการปัญหา
</Link>
<Link className="rounded-md px-3 py-2 font-semibold text-slate-700 hover:bg-slate-100" href="/issues/new">
แจ้งปัญหาใหม่
</Link>
</div>
</nav>Navigation อยู่ใน Layout เพราะทุกหน้าใช้ร่วมกัน ส่วน flex-wrap ช่วยให้เมนูขึ้นบรรทัดใหม่เมื่อพื้นที่ไม่พอ
หลังตรวจหน้าเว็บแล้ว ให้ลบ .app-nav และ .app-nav-inner เดิมออกจาก app/globals.css
Slide 14: แปลง Header เป็น Tailwind
แก้บางส่วน: app/page.tsx
แทน <header>...</header> เดิมด้วย:
<header className="bg-teal-800 px-6 py-8 text-white">
<div className="mx-auto max-w-5xl">
<h1 className="text-3xl font-bold">ระบบแจ้งปัญหา IT</h1>
<p className="mt-2 text-teal-100">
แจ้งและติดตามปัญหาการใช้งานระบบภายใน
</p>
</div>
</header>- Header ชั้นนอกกำหนดสีและระยะห่างเต็มความกว้าง
<div>ชั้นในจำกัดความกว้างและจัดเนื้อหาให้อยู่กลาง
เมื่อผลลัพธ์ถูกต้อง ให้ลบ CSS เดิมของ header, header h1 และ header p
Slide 15: จัด Main และ Footer
แก้บางส่วน: app/page.tsx
เพิ่ม Tailwind Class ให้ <main> เดิม:
<main className="mx-auto grid grid-cols-1 max-w-5xl gap-6 px-6 py-8">
<IssueForm />
<IssueList issues={issues} />
</main>เพิ่ม Tailwind Class ให้ <footer> เดิม:
<footer className="px-6 py-8 text-center text-sm text-slate-500">
<p>ฝ่ายเทคโนโลยีสารสนเทศ</p>
</footer>grid grid-cols-1 gap-6ทำให้ Form และ List เรียงลงมาโดยมีระยะห่างสม่ำเสมอgrid-cols-1ทำให้ Grid Column ย่อตามความกว้างหน้าจอได้ เพื่อไม่ให้ Table ดันทั้งหน้าให้ล้นแนวนอนmax-w-5xlช่วยไม่ให้เนื้อหากว้างเกินไปบนหน้าจอขนาดใหญ่
ตรวจหน้าเว็บแล้วจึงลบ CSS เดิมของ main, footer และ section + section
Slide 16: แปลงโครง IssueForm
แก้บางส่วน: components/IssueForm.tsx
เพิ่ม Tailwind Class ให้ Element เดิม โดยไม่เปลี่ยนชื่อ Field:
<section className="rounded-lg border border-slate-200 bg-white p-6">
<h2 className="text-xl font-bold text-slate-950">แจ้งปัญหาใหม่</h2>
<form className="mt-4">
<fieldset className="grid gap-4">
<legend className="text-sm font-semibold text-slate-700">
ข้อมูลปัญหา
</legend>
<div className="grid gap-4 md:grid-cols-2">
{/* ชื่อและอีเมลผู้แจ้ง */}
</div>
{/* หัวข้อ รายละเอียด และปุ่ม */}
</fieldset>
</form>
</section>md:grid-cols-2 ทำให้ชื่อและอีเมลเรียงแนวตั้งบนมือถือ แล้วเปลี่ยนเป็นสองคอลัมน์เมื่อจอกว้างขึ้น
Slide 17: แปลง Field
ลงมือแก้ Project: components/IssueForm.tsx
ใช้รูปแบบนี้กับ Input และ Textarea ทุก Field:
<div className="grid gap-2">
<label htmlFor="title" className="text-sm font-semibold text-slate-800">
หัวข้อปัญหา
</label>
<input
id="title"
name="title"
type="text"
required
className="w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-teal-700 focus:outline-none focus:ring-4 focus:ring-teal-100"
/>
</div>ใช้ grid gap-2 ครอบ Label กับช่องกรอก แล้วนำ Class ชุดเดียวกันไปปรับ Field ชื่อ อีเมล และรายละเอียด โดยคง id, name, type และ required เดิมไว้
สำหรับ <textarea> ให้ใช้ Class ชุดเดียวกับ Input และเพิ่ม resize-y เพื่อให้ผู้ใช้ปรับขนาดได้เฉพาะแนวตั้งแทน CSS เดิม resize: vertical
Slide 18: แปลง Button
แทนปุ่มเดิมใน components/IssueForm.tsx ด้วย:
<button
type="submit"
className="rounded-md bg-teal-700 px-4 py-3 font-bold text-white hover:bg-teal-800 focus:outline-none focus:ring-4 focus:ring-teal-100"
>
ส่งข้อมูล
</button>bg-teal-700คือสีปุ่มปกติhover:bg-teal-800ทำให้สีเข้มขึ้นเมื่อชี้เมาส์focus:ring-4ช่วยบอกตำแหน่งเมื่อใช้ Keyboard- ต้องเก็บ
type="submit"ไว้ เพราะปุ่มนี้ใช้ส่ง Form
Slide 19: ลบ CSS เดิมของ Page Shell
หลังตรวจ Navigation, Header, Main และ Footer แล้ว ให้ลบ CSS Block เหล่านี้ทั้งก้อนออกจาก app/globals.css:
.app-nav { ... }
.app-nav-inner { ... }
header { ... }
header h1 { ... }
header p { ... }
main { ... }
footer { ... }
footer p { ... }
section + section { ... }ยังไม่ลบ section เพราะ IssueList ยังใช้ Style ของ Selector นี้อยู่จนถึง Hour 2
Slide 20: ลบ CSS เดิมของ Form
เมื่อ IssueForm.tsx ใช้ Tailwind ครบแล้ว ให้ลบ CSS Block เหล่านี้:
fieldset { ... }
legend { ... }
.form-group { ... }
label { ... }
input,
textarea { ... }
textarea { ... }
input:focus,
textarea:focus { ... }
button { ... }
button:hover { ... }ลบ Media Query ของ .form-row ด้วย เพราะแทนที่ด้วย md:grid-cols-2 แล้ว:
@media (min-width: 720px) {
.form-row { ... }
}ถ้า @media นี้มีเพียง .form-row ให้ลบทั้ง Block แต่ถ้ามีกฎอื่นอยู่ด้วย ให้ลบเฉพาะ .form-row
ยังเก็บ @import "tailwindcss";, *, body, section, .table-wrapper, Table และ .status-* ไว้ก่อน ส่วนของ Issue List จะย้ายใน Hour 2
Slide 21: ตรวจผลลัพธ์หลังจบ Hour 1
ตรวจหน้า /, /issues, /issues/new และ /issues/001:
- Navigation ยังแสดงทุกหน้า
- หน้า Home, Main และ Footer มีระยะห่างถูกต้อง
- Form อ่านง่ายทั้งจอเล็กและจอกว้าง
- Browser ไม่มี Scroll แนวนอนจาก Layout
- Console และ Terminal ไม่มี Error
Hour ถัดไปเราจะย้าย Style ของ StatusBadge และ IssueList แล้วทำ Table ให้รองรับหน้าจอเล็ก