Cheat sheet · Frontend · Basic HTML & CSS

HTML แท็กที่ใช้จริง · CSS · Flexbox · State — แผ่นเดียวจบ

คู่กับ บทเรียน 56–62 · ทุกตัวเลขในหน้านี้ยกมาจากการวัดจริงในบทเรียน ไม่ได้คำนวณในหัว

หน้านี้ไม่ได้เขียนไว้ให้อ่านรวดเดียว — เขียนไว้ให้เปิดตอนมือติดคีย์บอร์ด
ถ้ากำลังรีบ: หัวข้อ 8 (Flexbox ตัวไหนใส่ที่แม่ ตัวไหนใส่ที่ลูก) · หัวข้อ 9 (row กับ column สลับความหมายกันยังไง) และหัวข้อ 11 (ตารางกับดักรวมทั้งกลุ่ม) คือสามอันที่จะได้ใช้บ่อยที่สุด
ทุกตารางเรียงไว้ให้กวาดตาหาของเจอ ไม่ใช่ให้ไล่อ่าน — เจอของแล้วกลับไปอ่านรายละเอียดในบทที่วงเล็บบอกไว้

🧰 ตรวจด้วยอะไร เวอร์ชันไหน — ทุกตัวเลขและทุกผลรันในหน้านี้ ยกมาจากบทเรียน 56–62 ซึ่งวัดไว้ด้วยของพวกนี้ (รันวันที่ 27 สิงหาคม 2026)

Chrome 148.0.7778.280 (macOS)      # บท 58 · 62
Chromium 148 (engine ของ Chrome)   # บท 57 — วัดด้วย getComputedStyle() / getBoundingClientRect()
Google Chrome 151.0.7922.174       # บท 59 — วัดสีที่ออกมาจริง
Google Chrome (headless) macOS     # บท 60 · 61 — วัดขนาดกล่องจริง
python3 -m http.server 8756        # บท 56 · 58 — static file server ที่ใช้ยิง curl / ดักอ่านฟอร์ม

ตัวเลขที่ขึ้นกับฟอนต์ในเครื่อง (เช่น 39.89 × 20 ของ <span> หรือ 116px / 176px ของ flex-grow: 1) จะขยับได้นิดหน่อยบนเครื่องคุณ — ที่ต้องตรงคือทิศทาง ของผล ไม่ใช่ทศนิยม

สารบัญ

  1. โครงไฟล์ HTML ขั้นต่ำ + วิธีต่อ CSS
  2. แท็ก HTML ที่ใช้บ่อย — block · inline · semantic
  3. แท็กฟอร์มครบชุด + attribute ที่ขาดไม่ได้
  4. selector + ตาราง specificity
  5. box model — padding · margin · border
  6. หน่วย px % rem em vh vw
  7. display — block · inline · inline-block · none
  8. Flexbox — ตัวไหนใส่ที่แม่ ตัวไหนใส่ที่ลูก
  9. justify-content / align-items — row เทียบ column
  10. pseudo-class ของ state + ข้อควรระวัง
  11. ตารางกับดักรวมทั้งกลุ่ม
  12. ที่ฝึกต่อ และคู่มือที่ควรรู้ว่ามีอยู่
  13. อ้างอิง

1 · โครงไฟล์ HTML ขั้นต่ำ + วิธีต่อ CSS

เซฟเป็นไฟล์ .html แล้วดับเบิลคลิกได้เลย ไม่ต้องมี server ไม่ต้อง build (บท 56)

<!DOCTYPE html>
<html lang="th">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Joey's Resume</title>
  </head>
  <body>
    <h1>Resume</h1>
    <p>Hello, My name is Joey Wasin</p>
  </body>
</html>
บรรทัดมีไว้ทำอะไรลืมแล้วเกิดอะไร
<!DOCTYPE html>บอกว่า "อ่านไฟล์นี้ด้วยกฎยุคปัจจุบัน"สลับไป quirks mode · document.compatMode เป็น BackCompat · หน้าดูปกติทุกอย่าง แต่ <td> ไม่รับ font-size ที่สืบทอดมา (ได้ 16px แทน 30px)
<html lang="th">รากของเอกสาร · lang บอกภาษาเนื้อหาโปรแกรมอ่านหน้าจอออกเสียงผิดภาษา · เบราว์เซอร์เสนอแปลหน้าผิด ๆ
<head>ของที่ไม่แสดงบนหน้า — ชื่อแท็บ · การเชื่อม CSS · metaเบราว์เซอร์สร้างให้เอง · แต่ถ้าเอา <h1> ไปวางก่อน <title> ของที่เหลือตกไปอยู่ใน <body> หมด
<meta charset="UTF-8">บอกวิธีเข้ารหัสตัวอักษรภาษาไทยกลายเป็น สวัส… ทั้งหน้า · document.characterSet เดาเป็น windows-1252
<meta name="viewport" …>บอกมือถือให้ใช้ความกว้างจอจริงเปิดบนมือถือแล้วตัวหนังสือเล็กเท่ามด ต้องซูมเอง
<title>ชื่อบนแท็บและในผลค้นหาแท็บโชว์ชื่อไฟล์หรือ URL ดิบ ๆ แทน
<body>ทุกอย่างที่มองเห็นอยู่ในนี้เบราว์เซอร์สร้างให้เอง · แต่ไม่มีอะไรข้างในก็ไม่มีอะไรให้ดู

เอา CSS มาต่อกับ HTML — มีสามทาง ใช้จริงทางเดียว

วิธีเขียนยังไงใช้เมื่อไหร่
ไฟล์แยก (external)<link rel="stylesheet" href="style.css"> ใน <head>ค่าเริ่มต้นที่ควรใช้เสมอ — ไฟล์เดียวคุมได้ทุกหน้า และเบราว์เซอร์แคชไว้ใช้ซ้ำได้
ฝังในไฟล์ (internal)<style> … </style> ใน <head>ไฟล์เดียวจบ เช่นตัวอย่างสอน · หน้าเดียวโดด ๆ · อีเมล HTML
ติดที่ตัว element (inline)<p style="color: red">เกือบไม่ควรใช้ · มันชนะกฎกลางเสมอ (ยกเว้น !important) แก้แล้วต้องไล่หาทีละจุด

นับ request ให้เป็น หนึ่งหน้าเว็บ ≠ หนึ่ง request · เบราว์เซอร์ยิงเพิ่มเองทุกครั้งที่เจอ href / src ชี้ไปหาไฟล์อื่น — หน้าที่อ้าง CSS 1 · รูป 3 · สคริปต์ 2 = 7 request (ตัวหน้า HTML เองนับด้วย)

2 · แท็ก HTML ที่ใช้บ่อย — block · inline · semantic

เส้นแบ่งเส้นเดียวที่อธิบายได้ทั้งหมด: block กินทั้งบรรทัดและดันตัวถัดไปลงบรรทัดใหม่ · inline แทรกในบรรทัดและกว้างเท่าเนื้อข้างใน (วัดจริงในหน้ากว้าง 482px: div ได้ 482px ทั้งคู่คนละแถว · span ได้ 95px กับ 94px แถวเดียวกัน)

แท็กพันธุ์ใช้ตอนไหนจุดพลาดที่เจอบ่อยที่สุด
<div>blockมัดของเป็นก้อน เมื่อไม่มีแท็กที่มีความหมายตรงกว่าใส่ซ้อนกันโดยที่ยังตอบไม่ได้ว่าจะแปะ class อะไร
<p>blockย่อหน้าข้อความเอา block ไปใส่ข้างใน แล้วเบราว์เซอร์แอบปิด </p> ให้
<h1>–<h6>blockหัวข้อ · เลือกระดับตามโครงเรื่อง ไม่ใช่ตามขนาดตัวอักษรเลือกตามขนาดแล้วข้ามระดับ (h2 → h4)
<span>inlineจับคำกลางประโยคเพื่อจัดสไตล์สั่ง width/height แล้วงงว่าไม่มีผล (ได้ 118×19 ไม่ใช่ 120×60)
<header>blockส่วนหัวของหน้า (หรือของบทความ)ใช้ <div> แทนทั้งที่ตัวจริงมีอยู่ — หน้าตาไม่ต่างสักพิกเซล (วัดได้ 482px · display: block เหมือนกันหมดทั้งสี่) ประโยชน์อยู่ที่ landmark ของ screen reader · Google · และคนที่มาแก้โค้ดต่อ
<main>blockเนื้อหาหลัก · หน้าละหนึ่งอัน
<footer>blockท้ายหน้า · ลิขสิทธิ์ · ลิงก์ติดต่อ
<img>inlineใส่รูป · เป็น void element ไม่มีแท็กปิดลืม alt หรือใส่ alt เป็นชื่อไฟล์
<a> · <strong>inlineลิงก์ · เน้นข้อความเหมือน <span> — เมิน width/height

ขนาดเริ่มต้นจริงของหัวข้อ (วัดในหน้าเปล่า ไม่มี CSS เลย · ข้อความปกติ = 16px)

h1h2h3h4h5h6
32px24px18.72px16px13.28px10.72px
2 เท่า1.5 เท่า1.17 เท่าเท่ากันพอดีเล็กกว่าเล็กกว่ามาก
⚠ alt="" กับ "ไม่ใส่ alt" ไม่ใช่สิ่งเดียวกัน

ตาแยกไม่ออก แต่ screen reader แยกออก — ทดสอบด้วย <img> ที่ชี้ไปไฟล์ที่ไม่มีจริง สามอัน:

เขียนแบบตาเห็นscreen reader ได้ยิน
alt="แมวส้มนอนบนคีย์บอร์ด"ข้อความนั้นแทนรูป · กล่องหดเหลือ 171×26 ไม่ใช่ 200×60 ตามที่สั่งข้อความนั้น
alt=""กล่องเปล่า 200×60 ตามที่สั่งข้ามเงียบ ๆ — ถูกต้องสำหรับรูปประดับ
ไม่ใส่ altกล่องเปล่า 200×60 — เหมือนอันบนเป๊ะไม่รู้ว่าควรข้ามไหม ส่วนใหญ่เลยอ่านชื่อไฟล์ออกมาดัง ๆ

คำถามที่ใช้เขียน alt ให้ถูกทุกครั้ง: "ถ้ารูปนี้หายไป ผู้อ่านเสียข้อมูลอะไร" — เสีย → เขียนข้อมูลนั้นลงไป · ไม่เสียอะไร (ข้อความข้าง ๆ บอกอยู่แล้ว) → alt="" · ทุก <img> ต้องมี alt เสมอ คำถามมีแค่ว่าจะใส่ว่าอะไร

✗ p ครอบ div

<p>ก่อน<div>กลาง</div>หลัง</p>

<!-- DOM ที่ได้จริง (อ่านจาก innerHTML): -->
<p>ก่อน</p><div>กลาง</div>หลัง<p></p>

ได้กรอบ p สองอันจากที่พิมพ์อันเดียว · ไม่มี error ไม่มีคำเตือน

✓ div ครอบ p

<div>
  <p>…</p>
</div>

DOM ที่ได้ตรงกับที่พิมพ์ทุกตัวอักษร

HTML entity &lt; ได้ < · &gt; ได้ > · &amp; ได้ & · &copy; ได้ © — ต้องใช้เวลาอยากโชว์โค้ด HTML บนหน้าเว็บ ไม่งั้นเบราว์เซอร์นึกว่าเป็นแท็กจริง

3 · แท็กฟอร์มครบชุด + attribute ที่ขาดไม่ได้

แท็กทำอะไรattribute ที่ขาดไม่ได้
<form>"ซอง" ที่ลากเส้นล้อมของไว้ — ไม่มีหน้าตาของตัวเองaction (URL ปลายทาง · ไม่ใส่ = ส่งกลับมาที่หน้าตัวเอง) · method (GET/POST เท่านั้น · ไม่ใส่ = GET)
<input>ช่องกรอกบรรทัดเดียว · void element ไม่มีแท็กปิดtype · id (ให้ label เกาะ) · name (ไม่มี = ไม่ถูกส่ง) · required · placeholder · value
<label>ข้อความกำกับหน้าช่อง + ผูกตัวเองเข้ากับช่องfor ต้องตรงกับ id ทุกตัวอักษร (หรือเอา <input> ไปไว้ข้างใน <label> เลย ไม่ต้องมี for/id)
<button>ปุ่มtype — เขียนทุกปุ่มเสมอ ไม่ใช่เพราะเบราว์เซอร์ต้องการ แต่เพราะคนอ่านโค้ดต่อต้องการ
<textarea>พิมพ์ได้หลายบรรทัด · ลากมุมขยายได้id · name · rows · ไม่มี value — ค่าเริ่มต้นคือของที่อยู่ระหว่างแท็ก จึงควรเขียน </textarea> ปิดติดกันไปเลย
<select>เลือกจากรายการที่กำหนดไว้ พิมพ์เองไม่ได้id · name
<option>หนึ่งตัวเลือกใน <select>value — ใส่ให้ทุกอันเสมอ (ไม่ใส่ = ส่งข้อความข้างในไปแทน วันไหนมีคนแก้คำภาษาไทย ค่าใน database เปลี่ยนตาม) · disabled selected ใช้ทำ "หัวข้อหลอก"

type ของ <input> ที่ใช้บ่อย

typeได้อะไรเพิ่มจาก textเหมาะกับ
textค่าเริ่มต้น ไม่มีอะไรพิเศษชื่อ · ที่อยู่ · อะไรก็ได้
emailเบราว์เซอร์ตรวจรูปแบบให้ก่อนส่ง · มือถือขึ้นคีย์บอร์ดที่มี @อีเมล
passwordปิดบังตัวอักษรเป็นจุดรหัสผ่าน
numberมีลูกศรเพิ่ม/ลด · มือถือขึ้นคีย์บอร์ดตัวเลขอายุ · จำนวน
checkboxกลายเป็นช่องติ๊ก · ส่งค่าก็ต่อเมื่อถูกติ๊กยอมรับเงื่อนไข
radioเลือกได้อันเดียวในกลุ่มที่ name เหมือนกันเพศ · ตัวเลือกไม่กี่อัน

type ของ <button> — ในฟอร์ม ถ้าไม่ใส่ เบราว์เซอร์ถือว่าเป็น submit ให้อัตโนมัติ (อ่าน button.type จาก DOM ได้ "submit" ทั้งที่ไม่ได้เขียนไว้)

typeกดแล้วเกิดอะไรใช้ตอนไหน
submit (ค่าเริ่มต้น)รวบค่าทั้งฟอร์มส่งไป action แล้วโหลดหน้าใหม่ปุ่มส่งจริง ๆ ปุ่มเดียวของฟอร์ม
buttonไม่ทำอะไรเลย รอให้ JavaScript มาผูกงานให้ปุ่มอื่นทุกปุ่ม — ล้างช่อง · เพิ่มแถว · เปิด popup
resetคืนทุกช่องกลับเป็นค่าตั้งต้น (ไม่ใช่ค่าว่าง)แทบไม่ใช้แล้ว เพราะคนกดพลาดบ่อย

id กับ name — คนละหน้าที่กันคนละเรื่อง

idname
ใครใช้<label for> · CSS · JavaScriptตัวเบราว์เซอร์ตอนส่ง และ handler ฝั่ง Go
ต้องไม่ซ้ำไหมต้องไม่ซ้ำทั้งหน้าซ้ำได้ — radio กลุ่มเดียวกันต้องซ้ำด้วยซ้ำ
ไม่ใส่แล้วเป็นไงคลิก label ไม่โฟกัส · JS หาไม่เจอค่าไม่ถูกส่งไปเลย — เงียบสนิท ไม่มี warning
ตั้งชื่อยังไงดีตั้งให้อ่านง่ายในหน้านี้ตั้งให้ตรงกับ tag form:"…" ฝั่ง Go

ฟอร์มไปโผล่ตรงไหนในโค้ด Go — ฟอร์ม method="POST" ส่ง Content-Type: application/x-www-form-urlencoded ไม่ใช่ JSON (ดักอ่าน request จริงแล้วได้ BODY=fullname=%E0%B8%AA…&topic=consult&message=…)

ในฟอร์ม HTMLฝั่ง Gin หยิบด้วยหรือผูกเข้า struct ด้วย tag
<input name="fullname"> ในฟอร์ม POSTc.PostForm("fullname")form:"fullname" + c.ShouldBind(&req)
<input name="q"> ในฟอร์ม GETc.Query("q")form:"q" + c.ShouldBindQuery(&req)
<select name="topic">c.PostForm("topic")form:"topic" — ได้ value ของ option ที่เลือก
<textarea name="message">c.PostForm("message")form:"message" — เหมือน input ทุกอย่าง
⚠ ของฝั่งเบราว์เซอร์กันของเสียไม่ได้

type="email" ตรวจให้จริง (ใส่ค่าที่ไม่ใช่อีเมลแล้วกดส่ง ได้ Please include an '@' in the email address. และ event submit ไม่ถูกยิงออกมาด้วยซ้ำ) — แต่ใครก็ยิง curl ตรงเข้า endpoint ข้ามหน้าเว็บได้ ตัวที่กันของเสียจริงคือ binding:"required,email" ฝั่ง Go · และฟอร์ม HTML ล้วน ๆ ส่ง PUT/DELETE ไม่ได้ — ใส่ไปก็ตกกลับไปใช้ GET เงียบ ๆ

4 · selector + ตาราง specificity

กฎ CSS หนึ่งข้อมีสองส่วนเสมอ: ฝั่งซ้ายของปีกกา = "จะจัดใคร" · ฝั่งในปีกกา = "จัดยังไง" (คู่ property: value; หนึ่งคู่ = หนึ่ง declaration)

h1 { color: olive; font-size: 48px; } ─┬ ─────┬───── ──────┬────── │ │ │ │ │ └─ อีกคำสั่งหนึ่ง (คั่นด้วย ; ) │ └─ คำสั่งหนึ่งคำสั่ง = ชื่อสิ่งที่จะเปลี่ยน : ค่าที่ต้องการ ; └─ selector — "จะจัดใคร"
แบบเขียนยังไงจับอะไรในหน้าหนึ่งมีได้กี่ตัว
tag (type selector)h1 { } — ชื่อ element เปล่า ๆ ไม่มีสัญลักษณ์นำหน้าทุก element ที่เป็นชนิดนั้น ทั้งหน้ากี่ตัวก็ได้ — ไม่ได้เลือก มันจับหมด
class.sub-title { } — นำหน้าด้วยจุดทุก element ที่เขียน class="sub-title" ไว้กี่ตัวก็ได้ · หนึ่ง element ติดได้หลาย class (class="btn danger")
id#about-me { } — นำหน้าด้วยชาร์ปelement ตัวเดียวที่เขียน id="about-me"ชื่อหนึ่ง ๆ ห้ามซ้ำในหน้าเดียวกัน

specificity — น้ำหนักสามช่อง (id, class, tag)

selectorน้ำหนัก (id, class, tag)อ่านว่า
h40, 0, 1tag หนึ่งตัว
.warning0, 1, 0class หนึ่งตัว
#top1, 0, 0id หนึ่งตัว
h4.warning0, 1, 1class หนึ่ง + tag หนึ่ง
.btn.danger0, 2, 0class สองตัวบน element เดียวกัน
body div p0, 0, 3tag สามตัวซ้อนกัน
.btn:hover เทียบ .btn:activeเท่ากันเป๊ะclass หนึ่งตัว + pseudo-class หนึ่งตัว ทั้งคู่ → ตัดสินด้วยลำดับ
⚠ สามช่องนี้ไม่ใช่เลขฐานสิบ

เทียบจากช่องซ้ายสุด ใครมากกว่าชนะทันที ช่องขวาไม่มีสิทธิ์แซง — .mark (0,1,0) ชนะ body div p (0,0,3) ทั้งที่ tag มีตั้งสามตัวและเขียนทีหลัง (วัดสีจริงได้ rgb(0, 128, 0) = green · ลองยืดเป็น tag สิบเอ็ดตัวซ้อนกันก็ยังเขียว)

ลำดับการตัดสิน — จำผิดแล้วจะไปนั่งสลับบรรทัดทั้งวันโดยหน้าไม่เปลี่ยนเลย

  1. ดูน้ำหนักก่อนเสมอ — id ชนะ class ชนะ tag (วัดจริง: tag เปล่า = teal · +class = orange · +id = crimson) · ไม่เกี่ยวกับใครเขียนก่อนหลังเลย
  2. เสมอกันเมื่อไหร่ ค่อยดูว่าใครเขียนทีหลัง (source order) — บรรทัดล่างชนะ
สิ่งที่อยากทำใช้อะไรเพราะอะไร
ตั้งฟอนต์ · สีตัวหนังสือ · ขนาดพื้นฐานของทั้งหน้าtag เช่น body { }อยากให้โดนทุกตัวจริง ๆ และน้ำหนักต่ำสุดพอดี ทับทีหลังได้ง่าย
ปุ่ม · การ์ด · ป้ายสถานะ · อะไรก็ตามที่มีมากกว่าหนึ่งอันclass ← ค่าเริ่มต้นของงานส่วนใหญ่ใช้ซ้ำได้ · น้ำหนักเท่ากันหมดจึงเดาผลได้จากลำดับ · ถอดออกก็ง่าย
จุดเดียวในหน้าที่หน้าตาไม่เหมือนใครเลยclass ที่ตั้งชื่อเฉพาะ เช่น .hero-titleได้ความเจาะจงที่ต้องการโดยไม่ต้องรับน้ำหนักของ id มาด้วย
ลิงก์กระโดดไปหัวข้อ · ผูก label กับช่องกรอก · ให้ JS หยิบ elementidนี่คืองานที่ id ถูกออกแบบมาเพื่อมัน และไม่มีอะไรทำแทนได้
กฎง่าย ๆ ที่พกไปใช้ได้เลย

เขียน id ใน HTML ได้เต็มที่ — แต่ในไฟล์ CSS อย่าให้มี # โผล่มาเลย
วันไหนรู้สึกว่า "ต้องใช้ # แล้วล่ะ ไม่งั้นสู้ไม่ได้" นั่นคือสัญญาณว่ามีกฎข้ออื่นแรงเกินไปอยู่ก่อนแล้ว — ให้ไปลดน้ำหนักกฎนั้น อย่าเพิ่มน้ำหนักฝั่งเรา

5 · box model — padding · margin · border

+-----------------------------------------+ | MARGIN (ไม่ทาพื้นหลัง) | | +-----------------------------------+ | | | BORDER (เป็นเส้นเอง) | | | | +-----------------------------+ | | | | | PADDING (ทาพื้นหลัง) | | | | | | +---------------------+ | | | | | | | CONTENT | | | | | | | +---------------------+ | | | | | +-----------------------------+ | | | +-----------------------------------+ | +-----------------------------------------+
ชั้นคืออะไรพื้นหลังทาถึงไหม
contentตัวข้อความ รูป หรือกล่องลูกที่อยู่ข้างในทา
paddingที่ว่างด้านใน ระหว่างเนื้อหากับขอบกล่องทา
borderเส้นกรอบ — มีความหนาจริง กินที่จริงเป็นเส้นเอง
marginที่ว่างด้านนอก ระหว่างกล่องนี้กับกล่องอื่นไม่ทา — โปร่งใสเสมอ

ช่องขวาสุดคือวิธีดูด้วยตาเปล่า: ทาสีพื้นให้กล่องแล้วดูว่าสีขยายตามไปด้วยไหม — ขยาย = padding · ไม่ขยาย = margin

เขียนค่าได้กี่แบบ — padding กับ margin ใช้กติกาเดียวกันเป๊ะ จำครั้งเดียวได้ทั้งคู่

เขียนแบบตัวอย่างแปลว่า
1 ค่าpadding: 64px;ทั้งสี่ด้าน 64
2 ค่าpadding: 120px 64px;บน/ล่าง 120 · ซ้าย/ขวา 64 — แบบที่เจอบ่อยที่สุดในโค้ดจริง
4 ค่าpadding: 120px 64px 120px 64px;บน · ขวา · ล่าง · ซ้าย (วนตามเข็มนาฬิกา)
แยกด้านpadding-top: 120px;ระบุทีละด้าน มีครบสี่ตัว -top -right -bottom -left

margin มีของแถมที่ padding ไม่มีสองอย่าง: รับค่าติดลบได้ (ดึงกล่องให้ทับกัน) และ รับคำว่า auto — margin: 0 auto; คือวิธีจัดกล่องให้อยู่กลางแนวนอน

border เป็น shorthand รวมสามค่า: ความหนา · รูปแบบ · สี · ไม่ใส่ style เส้นจะไม่ขึ้นเลย เพราะค่าเริ่มต้นของ border-style คือ none — เขียน border: 4px red; อย่างเดียวจึงไม่เห็นอะไรทั้งนั้น

border: 4px solid #495057;      /* shorthand — ใช้แบบนี้เกือบตลอด */
border-width: 4px;              /* เขียนแยกเมื่อต้องแก้เฉพาะบางส่วนทีหลัง */
border-style: solid;             /* เช่น border-color: red; ตอน hover */
border-color: #1971c2;
border-radius: 12px;
⚠ กับดักที่ 1 — width: 100% แล้วล้น

กรอบพ่อ 300px · ลูกสั่ง width: 100%; padding: 20px; border: 4px → วัด getBoundingClientRect() ได้ 348px ไม่ใช่ 300

300 (content) + 20×2 (padding) + 4×2 (border) = 348   /* content-box = ค่าเริ่มต้น */
box-sizing: border-box;                              /* → ได้ 300 พอดี · content หดเหลือ 252 */

border-box ไม่ได้ทำให้ padding หายไป — มันเบียดที่ของ content แทน · ไม่มี error ไม่มี warning · รู้ตัวตอนเห็น scrollbar แนวนอนโผล่มาเท่านั้น · นี่คือเหตุผลที่โปรเจกต์จริงเกือบทุกโปรเจกต์เขียน * { box-sizing: border-box; } ไว้บรรทัดแรกของไฟล์ CSS แล้วไม่ต้องคิดเรื่องนี้อีกเลย

⚠ กับดักที่ 2 — margin collapsing

กล่องบน margin-bottom: 40px · กล่องล่าง margin-top: 24px → ช่องว่างจริง 40px ไม่ใช่ 64

margin แนวตั้งของสองกล่องที่ติดกันไม่บวกกัน — มันยุบรวมเป็นก้อนเดียวแล้วเอา max(40, 24) = 40 · แก้ 24 เป็น 30 ก็ยังได้ 40 (ทุกค่าที่น้อยกว่า 40 ให้ผลเหมือนกันหมด)

เกิดเฉพาะแนวตั้ง ไม่เกิดแนวนอน · และยัง "ทะลุ" ออกจากกล่องพ่อได้ด้วย — พ่อ div เปล่า ๆ ที่ลูกสั่ง margin-top: 30px → ขอบบนของพ่ออยู่ตำแหน่งเดียวกับลูกเป๊ะ และพ่อสูงแค่ 18px · เติม padding-top: 1px ให้พ่อ ระยะกลายเป็น 31px ทันที — 1 pixel เดียวหยุดการยุบได้

ทางออกที่คนสมัยนี้ใช้: ทำพ่อเป็น display: flex แล้วใช้ gap — ข้างในกล่อง flex ไม่มีการยุบรวม (ชุดเดิมเป๊ะ พอสั่ง display: flex; flex-direction: column; ระยะกลายเป็น 64px ทันที)

6 · หน่วย px % rem em vh vw

หน่วยอ้างอิงจากใช้ตอนไหน
pxไม่อ้างอิงอะไร — เป็นค่าคงที่ความหนาเส้น · ระยะเล็ก ๆ ที่ไม่ควรขยับ
%ขนาดของกล่องพ่อความกว้างที่ต้องยืดตามพ่อ
remfont-size ของ <html> (ปกติ 16px)ค่าเริ่มต้นที่ควรใช้ สำหรับ font-size และระยะห่าง
emfont-size ของตัวมันเอง — ยกเว้นตอนใช้กับ font-size ที่จะอ้างจากพ่อระยะที่ต้องโตตามตัวอักษร เช่น padding ของปุ่ม
vw1% ของความกว้างหน้าจอของที่ต้องเต็มจอ เช่น hero banner
vh1% ของความสูงหน้าจอsection สูงเต็มจอ (100vh)
⚠ สามข้อที่วัดมาแล้ว ไม่ใช่คิดในหัว
โจทย์ค่าที่วัดได้จริงเพราะ
พ่อ 20px → ลูก 1.5em → ลูกของลูก 1.5em20px → 30px → 45pxem ทบต้น — ชั้นสองคือ 1.5 ของ 30 ไม่ใช่ 1.5 ของ 20 · ในไฟล์ CSS เห็นแค่ 1.5em เท่ากันทุกชั้น ต้องเปิด DevTools อ่าน computed value ถึงจะรู้
ในโครงเดียวกัน ลูกสั่ง 1.5rem24px เสมอ (16 × 1.5)rem อ้างจากรากเดียวเสมอ ซ้อนกี่ชั้นก็ไม่เปลี่ยน
ลูก font-size: 32px ในพ่อ font-size: 20px สั่ง padding: 1em32px ไม่ใช่ 20pxem อ้างจากตัวมันเอง — คำว่า "อ้างจากพ่อ" ถูกเฉพาะตอนใช้กับ font-size เท่านั้น
พ่อกว้าง 200 สูง 300 · ลูกสั่ง padding: 10%20px ทั้งบนและซ้าย (ไม่ใช่ 30px ด้านบน)% ของ padding กับ margin คิดจากความกว้างของพ่อเสมอ ทุกด้าน — เป็นเหตุผลที่คนไม่ค่อยใช้ % กับระยะห่าง

จำง่าย ๆ: em = "เท่ากับตัวหนังสือของกล่องนี้" · ระยะห่างและขนาดตัวอักษรทั่วไปใช้ rem เก็บ em ไว้ใช้กับของที่อยากให้โตตามตัวหนังสือของมันเอง

font-family · background — สองข้อที่ควรรู้

7 · display — block · inline · inline-block · none

ค่าวางตัวยังไงwidth/height ใช้ได้ไหมใครเป็นแบบนี้อยู่แล้ว
blockขึ้นบรรทัดใหม่เสมอ · กว้างเต็มพ่อได้div · p · h1 · header · main · footer
inlineไหลไปกับข้อความ ไม่ขึ้นบรรทัดใหม่ไม่ได้ — โดนเมินเงียบ ๆspan · a · strong · img
inline-blockไหลไปกับข้อความ แต่ตั้งขนาดได้ได้button · input · select · textarea เป็นมาแต่กำเนิด (อ่านจาก getComputedStyle().display) · ที่ต้องสั่งเองคือ div กับ span
noneหายไปจากหน้าเว็บ ไม่กินที่เลยไม่มีความหมายไม่มี

วัดจริงกับ <span> ที่สั่ง width: 120px; height: 40px เหมือนกันเป๊ะ: inline ได้ 39.89 × 20 (เท่าข้อความ บวก border 1px สองข้าง — ทั้ง width และ height ถูกเมิน) · inline-block ได้ 122 × 42 (120 × 40 ตามสั่ง บวก border 1px สองข้าง) · block เด้งลงบรรทัดใหม่ · none ไม่มีกล่องที่สี่เลย

⚠ display: none ต่างจาก "ซ่อนไว้"

ไม่กินที่แม้แต่ pixel เดียว (กล่องสูง 30px สามใบ ใบกลางสั่ง none → รวมสูง 60px ไม่ใช่ 90px) และโปรแกรมอ่านหน้าจอก็อ่านไม่เจอด้วย · อยากซ่อนแค่ทางสายตาแต่ยังกินที่เดิม → visibility: hidden;

อยากเว้นที่... | +-- ระหว่างเนื้อหากับขอบกล่องตัวเอง? ----> padding | +-- ระหว่างกล่องนี้กับกล่องอื่น? --------> margin (ระวังยุบรวม) | หรือ gap ถ้าพ่อเป็น flex/grid | +-- อยากได้เส้นกรอบด้วย? --------------> border (อย่าลืม style) ตั้ง width/height แล้วผลไม่ตรง... | +-- ล้นออกนอกพ่อ? --------------------> box-sizing: border-box | +-- ไม่มีผลเลย? ----------------------> element เป็น inline อยู่

8 · Flexbox — ตัวไหนใส่ที่แม่ ตัวไหนใส่ที่ลูก

นี่คือตารางที่สำคัญที่สุดของทั้งหน้า

ใส่ผิดที่ไม่ error แต่จะไม่มีอะไรเกิดขึ้นเลย — และ display: flex ใส่ที่กล่องแม่ แล้วมันไปเปลี่ยนพฤติกรรมของลูก ไม่ใช่ของตัวมันเอง

ใส่ที่ กล่องแม่ (flex container)ทำอะไรใส่ที่ กล่องลูก (flex item)ทำอะไร
display: flexเปิดโหมด flex — ลูกชั้นแรกเรียงต่อกันflex-growถ้ามีที่ว่างเหลือ ขอรับไปกี่ส่วน (เริ่มต้น 0)
flex-directionย้ายแกนหลักflex-shrinkถ้าที่ไม่พอ ยอมหดกี่ส่วน (เริ่มต้น 1)
flex-wrapยอมขึ้นบรรทัดใหม่ไหมflex-basisออกตัวที่ขนาดเท่าไหร่ ก่อนแบ่งที่ว่าง (เริ่มต้น auto)
justify-contentจัดตำแหน่งตามแกนหลักflexรูปย่อของสามตัวบน
align-itemsจัดตำแหน่งตามแกนรองalign-selfแหกค่าของแม่เฉพาะตัวนี้
gap · row-gap · column-gapระยะห่างระหว่างลูกorderสลับลำดับที่วาด

แกนหลัก (main axis) กับแกนรอง (cross axis) — ไม่ใช่ "แนวนอน" กับ "แนวตั้ง" แต่เป็น "ตามแถว" กับ "ขวางแถว"

flex-direction: row (ค่าเริ่มต้น) flex-direction: column แกนหลัก ───────────────▶ แกนรอง ──────────▶ ┌───────────────────────────┐ ┌───────────────────────┐ แ │ ┌────┐ ┌────┐ ┌────┐ │ แ │ ┌────┐ │ ก │ │ 1 │ │ 2 │ │ 3 │ │ ก │ │ 1 │ │ น │ └────┘ └────┘ └────┘ │ น │ └────┘ │ ร │ │ ห │ ┌────┐ │ อ │ │ ล │ │ 2 │ │ ง │ │ ั │ └────┘ │ │ │ │ ก │ ┌────┐ │ ▼ └───────────────────────────┘ │ │ │ 3 │ │ ▼ │ └────┘ │ └───────────────────────┘
flex-directionแกนหลักชี้ไปทางใช้ตอนไหน
row (ค่าเริ่มต้น)ซ้าย → ขวาnavbar · แถวปุ่ม · แถวการ์ด
row-reverseขวา → ซ้ายอยากให้ของชิ้นแรกใน HTML ไปโผล่ขวาสุด
columnบน → ล่างฟอร์ม · sidebar · การ์ดที่มีหัว-เนื้อ-ท้าย
column-reverseล่าง → บนกล่องแชตที่ข้อความใหม่อยู่ล่างสุด
⚠ reverse สลับแค่ที่ตา ไม่ได้สลับที่หู

ลำดับใน HTML ยังเหมือนเดิม — screen reader และการกด Tab ยังไล่ตามลำดับใน HTML อยู่ดี · คนที่ใช้คีย์บอร์ดจะเห็น focus กระโดดจากขวาไปซ้ายแบบงง ๆ · ถ้าลำดับสำคัญ ให้แก้ที่ HTML ไม่ใช่ที่ CSS

gap — สั่งครั้งเดียวที่แม่ ได้ช่องว่างระหว่างกล่องเท่านั้น ขอบนอกไม่โดน

เขียนแบบนี้เท่ากับอ่านว่า
gap: 8px;row-gap: 8px; column-gap: 8px;ห่างเท่ากันทุกทิศ
gap: 8px 16px;row-gap: 8px; column-gap: 16px;แถวก่อน คอลัมน์ทีหลัง — ห่างระหว่างบรรทัด 8 · ระหว่างกล่องในบรรทัด 16
row-gap: 8px;—ช่องว่างที่คั่นระหว่างแถว ซึ่งวัดกันในแนวตั้ง (เมื่อ row) — ชื่อมันหลอก
column-gap: 16px;—ระยะห่างระหว่างกล่องในบรรทัดเดียวกัน

flex-wrap — ค่าเริ่มต้นคือ nowrap แปลว่า "ยัดลงหนึ่งบรรทัดให้ได้ ไม่ว่าจะต้องบีบแค่ไหน"

/* แม่กว้าง 260px · gap 8px · ลูก 4 กล่อง กล่องละ width: 100px */
nowrap (ค่าเริ่มต้น) → วัดได้กล่องละ 59px   // (260 − 8×3) ÷ 4 = 59
wrap                → ได้ 100px ตามที่สั่ง   // กล่องที่ไม่พอตกไปบรรทัดใหม่

flex ย่อมาจากอะไร — ตัวที่เซอร์ไพรส์คนมากที่สุดคือ flex-basis: 0% ที่แอบมากับ flex: 1

เขียนย่อเท่ากับเขียนเต็มแปลว่า
flex: 1;flex-grow: 1; flex-shrink: 1; flex-basis: 0%;ลืมขนาดเนื้อหาไปเลย แล้วหารที่ว่างทั้งกล่องเท่า ๆ กัน
flex: auto;flex-grow: 1; flex-shrink: 1; flex-basis: auto;เริ่มจากขนาดเนื้อหา แล้วค่อยแบ่งที่ว่างที่เหลือ
flex: none;flex-grow: 0; flex-shrink: 0; flex-basis: auto;ขนาดเท่าเนื้อหาตายตัว ไม่โต ไม่หด
flex: 0 1 auto;—ค่าเริ่มต้นของทุก flex item ที่ไม่ได้สั่งอะไรเลย

วัดจริง — แม่กว้าง 300px · gap: 8px · ลูกสองกล่องเนื้อหายาวไม่เท่ากัน:

เขียนแบบกล่อง "สั้น"กล่อง "ยาวกว่ามาก"
flex-grow: 1116px176px
flex: 1146px146px — คือ (300 − 8) ÷ 2 · ไม่ขึ้นกับเนื้อหาเลย

อยากได้ "แบ่งเท่ากัน" → flex: 1 · อยากได้ "เนื้อหาสั้นก็แคบหน่อย" → flex: auto หรือ flex-grow: 1 เฉย ๆ

navbar ของจริง — ทั้งเรื่องมาบรรจบกันที่ของชิ้นเดียวที่เห็นบนเว็บทุกเว็บ

/* วิธีที่ 1 — ตัวคั่นเปล่า ๆ */
.bar     { display: flex; align-items: center; gap: 12px; }
.spacer  { flex: 1; }   /* พองจนเต็มช่องว่าง แล้วดันทุกอย่างหลังมันไปชิดขวา */

<div class="bar">
  <span class="brand">PEA DevPool</span>
  <a href="#">บทเรียน</a>
  <span class="spacer"></span>
  <a href="#">เข้าสู่ระบบ</a>
</div>

/* วิธีที่ 2 — ไม่เพิ่ม element เลย · ดีกว่าในแง่ accessibility */
.login { margin-left: auto; }   /* margin: auto ใน flexbox ดูดที่ว่างที่เหลือมาเป็นของตัวเอง */

/* วิธีที่ 3 — ห่อสามอันแรกไว้ใน div เดียว แล้วใส่ที่แม่ */
.bar { justify-content: space-between; }   /* เหมาะเมื่อมี "กลุ่มซ้าย" กับ "กลุ่มขวา" ชัดเจน */

9 · justify-content / align-items — row เทียบ column

กับดักที่ทุกคนเหยียบ

อย่าจำว่า "justify = แนวนอน" ให้จำว่า "justify = ตามแถว" แล้วถามต่อทุกครั้งว่า "แถวนี้มันเรียงไปทางไหน"
พิสูจน์แล้วด้วยกล่องสองกล่องที่ CSS เหมือนกันเป๊ะ (justify-content: flex-end; align-items: center;) ต่างกันแค่กล่องขวาเติม flex-direction: column หนึ่งบรรทัด — กล่องซ้ายได้ ชิดขวา + กลางแนวตั้ง ส่วนกล่องขวาได้ ชิดล่าง + กลางแนวนอน

เขียนอะไรrow (ค่าเริ่มต้น) — แกนหลักชี้ขวาcolumn — แกนหลักชี้ลง
justify-content: flex-start (เริ่มต้น)ชิดซ้ายชิดบน
justify-content: flex-endชิดขวาชิดล่าง
justify-content: centerกลางแนวนอนกลางแนวตั้ง
align-items: flex-startชิดบนชิดซ้าย
align-items: flex-endชิดล่างชิดขวา
align-items: centerกลางแนวตั้งกลางแนวนอน
align-items: stretch (เริ่มต้น)ลูกยืดเต็มความสูงของแม่เองลูกยืดเต็มความกว้างของแม่เอง

นี่คือคำตอบของคำถามที่คนถามกันมากที่สุดในโลก CSS — จัดกลางทั้งสองแกน คือ display: flex; justify-content: center; align-items: center; ซึ่งใช้ได้เหมือนกันทั้ง row และ column เพราะ center ของทั้งสองแกนคือจุดเดียวกัน

ค่าที่เหลือของ justify-content — "ที่ว่างที่เหลือไปกองอยู่ตรงไหน"

ค่าที่ว่างไปกองอยู่ตรงไหน
flex-start (ค่าเริ่มต้น)ท้ายแถวทั้งหมด — ของจึงชิดหัวแถว
flex-endหัวแถวทั้งหมด — ของจึงชิดท้ายแถว
centerแบ่งครึ่งไปหัวแถวกับท้ายแถวเท่ากัน
space-betweenแทรกอยู่ระหว่างของเท่านั้น หัวกับท้ายไม่มีเลย
space-aroundทุกกล่องได้ที่ว่างรอบตัวเท่ากัน → ขอบนอกจึงบางกว่าช่องกลางครึ่งหนึ่ง
space-evenlyทุกช่องเท่ากันหมด รวมช่องริมสองข้าง

ค่าที่เหลือของ align-items: stretch (ค่าเริ่มต้น) · flex-start · flex-end · center · baseline (เส้นฐานตัวอักษรตรงกัน ไม่ใช่ขอบกล่องตรงกัน — เห็นชัดตอนตัวหนังสือคนละขนาด)

รายละเอียดที่ทำให้ดูรู้จริง ค่าเริ่มต้นจริง ๆ ของ align-items ตามมาตรฐานคือคำว่า normal ไม่ใช่ stretch — แต่ normal เมื่ออยู่ใน flex container ให้ผลเหมือน stretch · คุยกันเร็ว ๆ พูดว่า "ค่าเริ่มต้นคือ stretch" ได้เลย แต่ถ้าเปิด DevTools แล้วเห็น normal จะได้ไม่ตกใจว่าใครไปแก้

10 · pseudo-class ของ state + ข้อควรระวัง

เขียนเป็นจริงตอนใช้กับต้องระวัง
:hoverตัวชี้ลอยอยู่เหนือ (ไม่เกี่ยวกับการคลิก)element อะไรก็ได้มือถือไม่มี — ห้ามซ่อนของสำคัญไว้หลังมัน
:focusคีย์บอร์ดคุยอยู่กับตัวนี้ (คลิกก็นับ)ตัวที่โฟกัสได้อย่าใส่ outline: none แล้วจบแค่นั้น
:focus-visibleโฟกัสอยู่ และ เบราว์เซอร์เห็นว่าควรโชว์กรอบ (คือตอนใช้คีย์บอร์ด)ตัวที่โฟกัสได้ต้องเขียนหลัง :focus ไม่งั้นโดนทับ
:activeกำลังกดค้างอยู่ (designer เรียก pressed)element อะไรก็ได้ต้องเขียนไว้ล่างสุด ไม่งั้น :hover ทับ
:disabled / :enabledมี / ไม่มี attribute disabledform control เท่านั้น<div> กับ <a> ใช้ไม่ได้ — กฎเงียบไปเฉย ๆ ไม่ error
ลำดับที่จำไว้แล้วไม่พลาด

ปุ่มทั่วไป: ปกติ → :hover → :focus-visible → :active → :disabled
ลิงก์ <a>: LVHA — :link → :visited → :hover → :active (จำว่า "LoVe—HAte")
เหตุผลเดียวกันทั้งคู่: pseudo-class พวกนี้ specificity เท่ากันหมด ตัวล่างสุดจึงชนะเสมอ — เรียงจาก "เกิดบ่อย/ถาวร" ไปหา "เกิดชั่วขณะ" แล้วจะไม่มีวันทับกันผิด

❌ ลบทิ้งเฉย ๆ ไม่วาดอะไรกลับมา

button:focus { outline: none; }

ลบกรอบทุกกรณี รวมทั้งตอนกด Tab · คนที่ใช้คีย์บอร์ดจะไม่รู้ว่าตอนนี้อยู่ปุ่มไหน — ไม่ได้มีแค่ผู้พิการ รวมถึงคนที่เมาส์เสียและคนที่ไม่อยากละมือจากคีย์บอร์ดด้วย

✅ ลบของ default แล้ววาดของเราแทน

.btn:focus         { outline: none; }
.btn:focus-visible { outline: 3px solid #1a73e8;
                     outline-offset: 3px; }

คลิกด้วยเมาส์ = ไม่มีกรอบค้าง · กด Tab มา = เห็นวงแหวนชัด · เขียน :focus ก่อน :focus-visible เสมอ

⚠ มือถือไม่มี hover

โทรศัพท์ไม่มีตัวชี้ที่ "ลอยอยู่เหนือ" อะไรได้ · เอาข้อมูลที่จำเป็นไปซ่อนหลัง :hover = คนใช้มือถือไม่มีทางเห็นมันเลย ไม่ใช่ "เห็นยาก"
กฎที่ปลอดภัย: :hover ใช้ตกแต่งได้ ใช้ซ่อนของไม่ได้
อยากเขียนสไตล์ที่เฉพาะเครื่องมีเมาส์จริงถึงใช้ ให้ครอบด้วย @media (hover: hover) { … } — ถามความสามารถของอุปกรณ์ ไม่ใช่ถามความกว้างจอ (แท็บเล็ตจอใหญ่ก็ไม่มี hover · หน้าต่างที่ย่อแคบบน desktop ก็ยังมีเมาส์)

⚠ disabled (attribute) ≠ :disabled (pseudo-class)

attribute disabled ใน HTML ทำสามอย่างให้ทันทีโดยไม่ต้องเขียนอะไรเลย: กดแล้ว event click ไม่ยิงออกมาเลย · กด Tab ข้ามมันไป · และค่าไม่ถูกส่งไปกับฟอร์ม · ส่วน CSS แค่ทำให้จาง ไม่ได้ปิดอะไรเลย — ต้องทำทั้งสองอย่าง

ทดสอบด้วย el.matches(…)ผลจริง
<input> ธรรมดา ตรงกับ :enabled ไหมtrue
<div> ตรงกับ :enabled ไหมfalse
<a href="#"> ตรงกับ :enabled ไหมfalse
<button> ที่อยู่ใน <fieldset disabled> ตรงกับ :disabled ไหมtrue — ปิดยกชุดได้
เรียก .click() ใส่ปุ่มที่ disabled — handler ยิงกี่ครั้ง0 ครั้ง (ปุ่มปกติได้ 1)

อยากได้พฤติกรรมของปุ่ม → ใช้ <button> จริง ๆ แล้วจะได้ :disabled · การข้าม Tab · และการปิด click มาฟรีทั้งชุด

"Dragged" ในสไลด์ รายการ state ในสไลด์ (enabled · hover · focused · pressed · disabled · dragged) คือรายการในภาษาของ design system ซึ่ง Material Design 3 ใช้คำชุดนี้เป๊ะ ไม่ใช่รายการ pseudo-class ของ CSS · ห้าตัวแรกมีให้ใช้ตรงตัว (:enabled · :hover · :focus · :active · :disabled) ส่วน :dragged ไม่มีอยู่จริง — ถามเบราว์เซอร์ตรง ๆ ด้วย CSS.supports('selector(:dragged)') ได้ false · ที่ทำได้ด้วย CSS ล้วนคือ cursor: grab คู่กับ :active { cursor: grabbing } · การลากจริงต้องใช้ JavaScript แปะ class เอง เช่น .is-dragging

11 · ตารางกับดักรวมทั้งกลุ่ม

เรียงตามอาการที่เห็นด้วยตา เพราะเวลาติดจริง คุณมีแค่อาการ ไม่ได้มีชื่อของปัญหา

อาการที่เห็นสาเหตุทางแก้
ภาษาไทยกลายเป็น สวัส… ทั้งหน้าลืม <meta charset="UTF-8"> · เบราว์เซอร์เดาเป็น windows-1252ใส่บรรทัดนั้นใน <head> (บท 56)
หน้าดูปกติทุกอย่าง แต่ document.compatMode ได้ BackCompatไม่มี <!DOCTYPE html> หรือมีอะไรอยู่ข้างหน้ามันใส่กลับที่บรรทัดแรกสุด — บั๊กที่รอ คือบั๊กที่แพงที่สุด (บท 56)
แก้สีที่ไฟล์ CSS กลางแล้ว แต่บางตัวไม่ยอมเปลี่ยนมี style="" ติดที่ตัว element ซึ่งชนะกฎกลางเสมอถอด inline style ออก ย้ายไปเป็น class (บท 56)
ใส่ padding ให้ <p> แล้วไม่ครอบของข้างใน · เจอ <p></p> เปล่าที่ไม่ได้พิมพ์เอา block ไปใส่ใน <p> · parser แอบปิด </p> ให้สลับข้าง — เอา <div> ครอบ <p> · ดู DOM จริงใน DevTools ไม่ใช่ไฟล์ต้นฉบับ (บท 57)
สั่ง width/height แล้วไม่เกิดอะไรขึ้นเลยelement นั้นเป็น inline (span · a · strong · img)เปิด DevTools ดู display ก่อนทำอย่างอื่น → inline-block (บท 57 · 60)
เปลี่ยน <div id="main"> เป็น <main> แล้วหน้าตาไม่เปลี่ยนเลยถูกแล้ว — วัดได้ 482px · display: block เหมือนกันเป๊ะประโยชน์อยู่ที่ landmark ซึ่งไม่โผล่บนหน้าจอ — อย่าตัดสินด้วย "เปลี่ยนแล้วเหมือนเดิม" (บท 57)
คลิกที่ข้อความของ label แล้วช่องไม่โฟกัสfor สะกดไม่ตรงกับ id · เบราว์เซอร์ไม่หาตัวที่ "ใกล้เคียง" ให้เลยทดสอบด้วยการคลิกที่ข้อความทุกอัน · ตรวจว่า id ไม่ซ้ำในหน้า (บท 58)
กดปุ่มแล้วหน้ากะพริบ แล้วทุกอย่างที่พิมพ์หายหมด<button> ในฟอร์มที่ไม่ใส่ type = submit โดยปริยายtype="button" ให้ทุกปุ่มที่ไม่ใช่ปุ่มส่ง — และเขียน type ให้ทุกปุ่มเสมอ (บท 58)
กรอกครบทุกช่อง แต่ฝั่ง Go อ่านได้ไม่ครบช่องนั้นไม่มี name — ไม่มีชื่อให้เขียน ก็ถูกข้ามเงียบ ๆใส่ name ให้ทุกช่องที่ต้องส่ง · ดู query string หลังกดส่งด้วยตาตัวเอง (บท 58)
คนแก้คำภาษาไทยใน dropdown แล้วค่าใน database เปลี่ยนตาม<option> ไม่มี value → ส่งข้อความข้างในไปแทนใส่ value ให้ทุก <option> เสมอ (บท 58)
ฝั่ง Gin อ่าน body ของฟอร์มไม่ออกฟอร์ม POST ส่ง application/x-www-form-urlencoded ไม่ใช่ JSONc.PostForm หรือ tag form:"…" — ไม่ใช่ c.ShouldBindJSON (บท 58)
เขียน CSS แล้วไม่เห็นอะไรเปลี่ยน · เปิด DevTools แล้วไม่เห็นกฎนั้นเลยลืมจุดหรือชาร์ปหน้า selector — sub-title { } กลายเป็นการหา element ชื่อ sub-titleเติม . / # · เบราว์เซอร์ไม่แจ้ง error ให้ มันเงียบไปเฉย ๆ (บท 59)
เห็นกฎในแผง Styles แต่ถูกขีดฆ่าคาไว้กฎติดแล้ว แต่แพ้ specificityลดน้ำหนักฝ่ายที่ชนะ (เลิกใช้ #id จัดสไตล์) อย่าเพิ่มน้ำหนักฝั่งเรา (บท 59)
นั่งสลับบรรทัดใน CSS ทั้งวันแล้วหน้าไม่เปลี่ยนเลยปัญหาอยู่ที่น้ำหนัก ไม่ใช่ลำดับ — ลำดับตัดสินก็ต่อเมื่อน้ำหนักเสมอกันเทียบสามช่อง (id, class, tag) จากซ้ายไปขวาก่อนเสมอ (บท 59)
กล่องล้นออกนอกพ่อ · scrollbar แนวนอนโผล่มาเองwidth: 100% ในโหมด content-box นับเฉพาะ content · padding + border บวกข้างนอก (300 → 348)box-sizing: border-box — โปรเจกต์จริงตั้ง * { box-sizing: border-box; } ไว้บรรทัดแรก (บท 60)
เพิ่มตัวเลข margin แล้วระยะห่างไม่ขยับเลยmargin แนวตั้งยุบรวมกัน แล้วใช้ค่าที่มากกว่า — max(40, 24) = 40อย่าเพิ่มตัวเลขมั่ว → เปลี่ยนไปใช้ gap ของ flex/grid จบเรื่องเร็วกว่า (บท 60)
ใส่ margin-top ให้ลูก แล้วทั้งก้อนเลื่อนลงไปทั้งอันmargin ทะลุออกนอกพ่อ เพราะพ่อไม่มี padding/border คั่นpadding-top: 1px ให้พ่อก็หยุดได้ · หรือทำพ่อเป็น flex (บท 60)
ตัวหนังสือบานใหญ่ผิดคาดทั้งที่ CSS เขียน 1.5em เท่ากันทุกชั้นem ทบต้น — 20 → 30 → 45pxใช้ rem เป็นค่าเริ่มต้น เก็บ em ไว้ใช้เฉพาะที่ตั้งใจ (บท 60)
ตั้ง width: 300px ให้การ์ด แล้วเปิดดูได้แคบกว่านั้นอยู่ใน flex container ที่เป็น nowrap แล้วโดน flex-shrink: 1 บีบเลือกตามเจตนา: flex-wrap: wrap ที่แม่ · flex-shrink: 0 ที่ลูก · หรือ min-width (บท 61)
ใส่ flex: 1 แล้วเงียบสนิทกล่องนั้นไม่ใช่ลูกชั้นแรกของ flex container (เป็นหลานอีกชั้น) หรือแม่ยังไม่ display: flexเช็คว่าใครเป็นแม่จริง ๆ · flex item = ลูกชั้นแรกเท่านั้น (บท 61)
ใส่ align-items: center แล้วไม่เห็นความต่างแม่ไม่ได้สูงกว่าเนื้อหา จึงไม่มีที่ว่างในแนวแกนรองให้จัดใส่ height หรือ padding ให้แม่ก่อน (บท 61)
ใส่ justify-content แล้วไม่ขยับไม่มีที่ว่างเหลือให้กระจาย เพราะของเต็มแถวพอดีอยู่แล้วดูว่าลูกกินที่หมดหรือยัง ก่อนไปสงสัยว่าเขียนผิด (บท 61)
flex-grow: -1 เขียนไปแล้วไม่ทำงาน แถวไม่มี error ใน Consoleค่าติดลบใช้ไม่ได้ — เบราว์เซอร์ทิ้งบรรทัดนั้นทั้งบรรทัดแล้วกลับไปใช้ค่าเริ่มต้นที่เดียวที่เห็นคือแผง Styles ใน DevTools ซึ่งขีดฆ่าบรรทัดนั้นไว้ให้ (บท 61)
flex: 1 กับ flex-grow: 1 ให้ผลไม่เท่ากันflex: 1 พ่วง flex-basis: 0% มาด้วย — 146/146 เทียบกับ 116/176อยากได้เท่ากัน → flex: 1 · อยากให้เนื้อหาสั้นแคบหน่อย → flex: auto (บท 61)
ใส่ URL ยาว ๆ ในการ์ดแล้วทั้งแถวล้นทะลุกรอบflex item หดได้ถึงแค่ min-content (ความกว้างของคำที่ตัดบรรทัดไม่ได้) แล้วหยุดmin-width: 0 ถึงจะยอมหดต่อ (บท 61)
กดปุ่มค้างด้วยเมาส์แล้วสีของ :active ไม่โผล่ตอนกดค้าง :hover ก็จริงด้วย · specificity เท่ากัน → ตัวที่เขียนทีหลังชนะย้าย :active ลงล่างสุด — แก้ด้วยการย้ายบรรทัด ไม่ใช่เพิ่ม !important (บท 62)
กด Tab แล้วไม่รู้ว่าตอนนี้อยู่ปุ่มไหนของหน้ามีคนเขียน button:focus { outline: none; } ทิ้งไว้โดยไม่วาดอะไรกลับมาย้ายกรอบไป :focus-visible · เบราว์เซอร์ไม่ใส่กลับมาให้เอง (บท 62)
เขียน .my-button:disabled ให้ <div> แล้วไม่มีอะไรเกิดขึ้น:disabled/:enabled ใช้กับ form control เท่านั้น — <div> ได้ falseใช้ <button> จริง ๆ · selector ที่ไม่ตรงกับอะไรถูกข้ามเงียบ ๆ ไม่เคยทำให้ไฟล์พัง (บท 62)
ปุ่มจางลงแล้ว แต่ผู้ใช้ยังกดได้อยู่CSS ทำให้จางอย่างเดียว ไม่ได้ปิดอะไรเลยแปะ attribute disabled ใน HTML ด้วยเสมอ (บท 62)
คนใช้มือถือบอกว่าไม่เห็นราคา/ปุ่มที่คุณเห็นอยู่ชัด ๆของนั้นถูกซ่อนไว้หลัง :hover ซึ่งมือถือไม่มีโชว์ตลอด แล้วใช้ :hover แค่เน้นของที่เห็นอยู่แล้ว (บท 62)

12 · ที่ฝึกต่อ และคู่มือที่ควรรู้ว่ามีอยู่

สองชุดนี้คนละหน้าที่กันเลย — ชุดแรกไว้ฝึกมือ ชุดที่สองไว้เปิดตอนต้องตัดสินใจ

เกมฝึกมือฝึกอะไรควรเล่นตอนไหน
CSS Dinerselector ล้วน ๆ 32 ด่าน ตั้งแต่ div จนถึง selector ซับซ้อนเล่นก่อนเพื่อนเลย — selector คือของที่ใช้ทุกวันและฝึกได้เร็วที่สุด
Flexbox Froggyflexbox 24 ด่าน — พากบไปเกาะใบบัวด้วย justify-content / align-itemsหลังบท 61 · ด่านละไม่ถึงนาที จบได้ในนั่งเดียว
Flexbox Zombiesflexbox เหมือนกัน แต่ยาวกว่าและลึกกว่า มี 12 บทแยกตาม propertyถ้าเล่น Froggy จบแล้วยังไม่มั่นใจ — ตัวนี้บังคับให้ใช้ property เดียวจนคล่องทีละบท
Coding Fantasyเกม RPG ที่ใช้ CSS (และภาษาอื่น) เป็นอาวุธเล่นเอาสนุก · ไม่ใช่ทางลัดของเรื่องไหนโดยเฉพาะ
คู่มือเอาไว้ตอบคำถามแบบไหนเข้าไปแล้วดูตรงไหนก่อน
CSS-Tricks Guides"property นี้มันทำงานยังไงกันแน่"หน้า A Complete Guide to Flexbox — เป็นหน้าที่คนทำเว็บทั้งโลกเปิดบ่อยที่สุดหน้าหนึ่ง · ใช้เป็นแผ่นภาพ ไม่ใช่บทเรียน
Material Design 3"ปุ่มตอน hover ควรเข้มขึ้นเท่าไหร่ ตอน pressed ควรเป็นยังไง"Foundations → Interaction states — ที่มาโดยตรงของรายการ state ในสไลด์
Apple HIG"ปุ่มควรใหญ่แค่ไหนถึงกดโดนบนมือถือ"หัวข้อ Inputs และ Foundations → Accessibility
WCAG 2.1"เว็บนี้คนที่มองไม่เห็น / ใช้เมาส์ไม่ได้ ใช้ได้ไหม"ข้อ 2.4.7 Focus Visible ข้อเดียวก่อน — สั้นมาก และมันคือหัวข้อ 10 ของหน้านี้ทั้งดุ้น

💬 ถามได้ เจออาการที่ไม่มีในตารางหัวข้อ 11 ส่งมาได้เลย — บอกมาว่าคาดว่าจะได้อะไร แต่ได้อะไรจริง สองอย่างนี้พอสำหรับหาสาเหตุแล้ว · ถ้าถาม AI ให้แปะโค้ดพร้อมประโยคนี้: "this is plain HTML/CSS, no framework — tell me what the browser does with it line by line"

อ้างอิง