บทความ

ทำ Tooltip ด้วย Tailwind CSS ยังไง ไม่ต้องเขียน JavaScript

ทำ tooltip ที่แต่งสไตล์เองได้ด้วย group-hover กับ group-focus-within ของ Tailwind CSS v4 ให้คนใช้คีย์บอร์ดและ screen reader เห็นด้วย แล้วแยกเป็น Blade component ไว้เรียกซ้ำ

เผยแพร่
อัปเดต
— ปรับเป็น Tailwind CSS v4 และรองรับคีย์บอร์ดกับ screen reader
เวลาอ่าน
4 นาที

เวลาทำ UI เรามักใช้ icon แทนปุ่ม action หรือบอกสถานะของรายการ แต่ icon อย่างเดียวบางทีก็สื่อไม่ชัด ผู้ใช้ไม่แน่ใจว่ากดแล้วจะเกิดอะไรขึ้น เราเลยอยากให้มีข้อความเล็ก ๆ โผล่ขึ้นมาตอนเอาเมาส์ไปวางบน icon ในบทความนี้เราจะทำ tooltip แบบนั้นด้วย Tailwind CSS v4 ล้วน ๆ ไม่ต้องเขียน JavaScript แล้วแยกเป็น Blade component ไว้ใช้ทั้งโปรเจกต์

attribute title ใช้ง่าย แต่แต่งสไตล์ไม่ได้

ทางที่ง่ายที่สุดคือใส่ attribute title แล้ว browser จะโชว์ tooltip ให้เอง

<button type="button" title="ดาวน์โหลด">
    <!-- Icon -->
</button>

แต่ต้องวางเมาส์รอสักพักกว่า tooltip จะขึ้น หน้าตาก็เป็นแบบของ browser เราเปลี่ยนสีหรือตำแหน่งไม่ได้ และ MDN เตือนไว้ ว่าคนที่ใช้จอสัมผัสหรือคีย์บอร์ดมักไม่เห็นข้อความใน title เลย เราจึงเขียน tooltip เองดีกว่า

วาง tooltip ไว้ใต้ icon ด้วย relative กับ absolute

เริ่มจากเขียนข้อความ tooltip เป็น element แยกไว้ข้าง ๆ ปุ่ม ถ้าปล่อยไว้แบบนี้ ข้อความจะต่อท้าย icon ไปเลย เราจึงหุ้มทั้งสองอย่างด้วย wrapper ที่ใส่คลาส relative แล้วให้ tooltip เป็น absolute เพื่อจัดตำแหน่งเทียบกับ wrapper

<div class="relative inline-block">
    <button type="button"><!-- Icon --></button>
    <div class="absolute top-full left-1/2 z-10 mt-2 -translate-x-1/2">ดาวน์โหลด</div>
</div>
  • top-full กับ mt-2 วาง tooltip ไว้ใต้ปุ่ม เว้นระยะนิดหน่อย
  • left-1/2 กับ -translate-x-1/2 จัดให้อยู่กึ่งกลางปุ่มพอดี
  • z-10 ให้ tooltip ลอยทับ element อื่นที่อยู่ข้างล่าง

อยากให้ขึ้นด้านบนก็เปลี่ยนเป็น bottom-full mb-2 อยากให้อยู่ด้านขวาก็ใช้ left-full ml-2 ดูคลาสจัดตำแหน่งทั้งหมดได้ที่ top / right / bottom / left

แต่งหน้าตา tooltip ให้เข้ากับธีม

ใส่สีพื้น สีตัวอักษร มุมโค้ง แล้วปิดท้ายด้วย whitespace-nowrap กันข้อความตัดขึ้นบรรทัดใหม่

<div class="... rounded-sm bg-gray-900 px-2 py-1 text-sm whitespace-nowrap text-white">ดาวน์โหลด</div>

บทความนี้เวอร์ชันแรกใช้ bg-gray-dark ซึ่งเป็นสีที่เราเพิ่มเองใน tailwind.config.js ส่วน v4 ไม่ใช้ไฟล์นั้นแล้ว เราประกาศสีของธีมใน CSS ด้วย @theme แทน

@import "tailwindcss";

@theme {
    --color-gray-dark: #273444;
}

ประกาศ --color-gray-dark แล้ว Tailwind สร้างคลาส bg-gray-dark text-gray-dark และคลาสสีอื่น ๆ ของชื่อนี้ให้เลย

ซ่อนไว้ก่อน แล้วโชว์ตอน hover หรือตอนปุ่มได้โฟกัส

ใส่คลาส group ที่ wrapper แล้วใช้ variant group-hover: ที่ tooltip แปลว่า "ตอนเมาส์อยู่บน wrapper ให้ tooltip เปลี่ยนเป็นแบบนี้" เราซ่อน tooltip ไว้ด้วย invisible opacity-0 แล้วโชว์ด้วย group-hover:visible group-hover:opacity-100 เติม transition-opacity อีกตัว tooltip ก็จะค่อย ๆ จางขึ้นมา

<div class="group relative inline-block">
    <button type="button"><!-- Icon --></button>
    <div class="invisible opacity-0 transition-opacity group-hover:visible group-hover:opacity-100 ...">ดาวน์โหลด</div>
</div>

แค่ hover ยังไม่พอ คนที่กด Tab ไล่ปุ่มด้วยคีย์บอร์ดจะไม่เห็น tooltip เลย และใน v4 hover กับ group-hover ทำงานเฉพาะอุปกรณ์ที่มีเมาส์ (Tailwind ครอบไว้ด้วย @media (hover: hover) ดู upgrade guide) เราจึงเพิ่ม group-focus-within:visible group-focus-within:opacity-100 ให้ tooltip ขึ้นตอนปุ่มข้างในได้โฟกัสด้วย

ส่วนบทความเวอร์ชันแรกที่ต้องเปิด variant group-hover ใน tailwind.config.js ก่อนนั้น เป็นข้อจำกัดของ Tailwind v2 ตั้งแต่ v3 ทุก variant ใช้ได้เลยโดยไม่ต้องตั้งค่าอะไร

ให้ screen reader อ่าน tooltip ด้วย role กับ aria-describedby

ใส่ role="tooltip" กับ id ที่ tooltip แล้วผูกกับปุ่มด้วย aria-describedby screen reader จะอ่านข้อความ tooltip ต่อจากชื่อปุ่ม ถึง tooltip จะซ่อนอยู่ก็อ่านได้ (ดู role tooltip บน MDN) ถ้าปุ่มมีแค่ icon และข้อความใน tooltip คือชื่อปุ่ม ให้ใช้ aria-labelledby แทน screen reader จะได้อ่านเป็นชื่อปุ่มเลย

รวมทุกอย่างแล้วได้แบบนี้

<div class="group relative inline-block">
    <button type="button" aria-describedby="tooltip-download">
        <!-- Icon -->
    </button>
    <div
        id="tooltip-download"
        role="tooltip"
        class="invisible absolute top-full left-1/2 z-10 mt-2 -translate-x-1/2 rounded-sm bg-gray-900 px-2 py-1 text-sm whitespace-nowrap text-white opacity-0 transition-opacity group-hover:visible group-hover:opacity-100 group-focus-within:visible group-focus-within:opacity-100"
    >
        ดาวน์โหลด
    </div>
</div>

tooltip สีเข้มใต้ปุ่ม icon สองแบบ ตอนเอาเมาส์วางกับตอนกด Tab มาโฟกัส ซ้าย: เอาเมาส์วางบนปุ่มดาวน์โหลด tooltip ขึ้นจาก group-hover ขวา: กด Tab มาที่ปุ่มแก้ไข tooltip ขึ้นจาก group-focus-within

ปุ่มชิดขอบจอ tooltip อาจล้นออกนอกจอ

tooltip แบบนี้วางตำแหน่งตายตัว ถ้าปุ่มอยู่ชิดขอบจอ ข้อความอาจล้นออกไปจนอ่านไม่ครบ CSS มี anchor positioning ที่ให้ tooltip ย้ายฝั่งเองเมื่อพื้นที่ไม่พอ แต่ตอนนี้ (ตุลาคม 2026) ยังรองรับไม่ครบทุก browser ระหว่างนี้ถ้าต้องการแบบนั้นจริง ๆ ก็ใช้ JavaScript อย่าง Floating UI ช่วยคำนวณตำแหน่ง

แยกเป็น Blade component ไว้เรียกซ้ำ

คลาสยาวขนาดนี้ไม่น่าก๊อปไปวางทุกที่ ถ้าใช้ Laravel เราแยกเป็น anonymous component ได้ รับ prop title เป็นข้อความ tooltip แล้วเอา icon หรือข้อความของปุ่มไปแสดงใน slot

@props(['title'])

@php
    $tooltipId = 'tooltip-'.Str::random(8);
@endphp

<div class="group relative inline-block">
    <button type="button" aria-describedby="{{ $tooltipId }}" {{ $attributes }}>
        {{ $slot }}
    </button>

    <div
        id="{{ $tooltipId }}"
        role="tooltip"
        class="invisible absolute top-full left-1/2 z-10 mt-2 -translate-x-1/2 rounded-sm bg-gray-900 px-2 py-1 text-sm whitespace-nowrap text-white opacity-0 transition-opacity group-hover:visible group-hover:opacity-100 group-focus-within:visible group-focus-within:opacity-100"
    >
        {{ $title }}
    </div>
</div>

component สุ่ม id ใหม่ทุกครั้งที่เรียก หน้าที่มีปุ่มหลายปุ่มจึงไม่มี id ซ้ำกัน ส่วน attribute อื่นที่เราส่งเข้ามา เช่น class หรือ wire:click จะไปอยู่ที่ปุ่มผ่าน $attributes

<x-tooltip title="ดาวน์โหลด" class="rounded-md p-2 text-gray-500 hover:bg-gray-100">
    <!-- Icon -->
</x-tooltip>

ถ้าสิ่งที่ต้องการให้มี tooltip เป็นลิงก์ ก็เปลี่ยน <button type="button"> ใน component เป็น <a> แล้วส่ง href เข้ามาทาง $attributes เหมือนกัน

อ่านต่อ

ล่าสุด

ดูทั้งหมด →