HTML 自訂資料屬性用來在元素上儲存應用程式專用資訊,名稱以 data- 開始。
<article
class="course-card"
data-course-id="html-basics"
data-level="beginner"
>
<h2>HTML 入門</h2>
</article>在這個例子中:
data-course-id提供程式使用的課程識別值。data-level儲存課程程度。- 這兩個屬性都不會自動顯示文字。
自訂資料屬性應用於應用程式資料,不應取代語意化 HTML、可見內容或無障礙屬性。
11.1 基本語法
自訂資料屬性具有以 data- 開始的名稱及對應值:
<p data-category="announcement">
課程指南現已提供。
</p>同一元素可以有多個資料屬性:
<article
data-course-id="css-basics"
data-level="beginner"
data-duration-hours="4"
>
<h2>CSS 入門</h2>
</article>實用命名慣例包括:
- 使用小寫字母。
- 使用連字號分隔詞語。
- 選擇具描述性的名稱。
- 避免在名稱內加入空格或不常見的標點符號。
- 在
data-之後加入有意義的名稱。
例如:
| 屬性 | 可能用途 |
|---|---|
data-course-id | 應用程式識別值 |
data-category | 篩選分類 |
data-duration-hours | 課程時數 |
data-action | 供 JavaScript 使用的操作識別值 |
小寫、以連字號分隔的名稱,方便配合 JavaScript 的 dataset 介面使用。
11.2 屬性值是字串
即使資料屬性值看起來像數字或布林值,它們仍然是文字:
<article
id="html-course"
data-duration-hours="3"
data-featured="false"
>
<h2>HTML 入門</h2>
</article>JavaScript 會將這些值讀取為字串:
const course = document.getElementById("html-course");
console.log(course.dataset.durationHours); // "3"
console.log(course.dataset.featured); // "false"有需要時,應明確轉換:
const duration = Number(course.dataset.durationHours);
const featured = course.dataset.featured === "true";不要用以下方式判斷字串 "false":
const featured = Boolean(course.dataset.featured);非空字串會被視為真值,因此結果是 true。
如果這些值會影響應用程式行為,應驗證它們,而不是假設格式正確。例如,Number() 會將空字串轉換成 0,無效的數字字串則可能得到 NaN。
11.3 使用 dataset 讀取資料
dataset 屬性讓 JavaScript 存取自訂資料屬性。
以連字號分隔的名稱,通常會轉換成駝峰式命名:
| HTML 屬性 | JavaScript 屬性 |
|---|---|
data-level | dataset.level |
data-course-id | dataset.courseId |
data-duration-hours | dataset.durationHours |
例如:
<article id="course-card" data-course-id="html-basics">
<h2>HTML 入門</h2>
</article>const card = document.getElementById("course-card");
if (card) {
console.log(card.dataset.courseId); // "html-basics"
}如果資料屬性不存在,對應的 dataset 屬性通常會回傳 undefined。
你也可以使用完整的屬性名稱:
const courseId = card.getAttribute("data-course-id");當屬性不存在時,getAttribute() 會回傳 null。
以上讀取方式都要求 card 是已找到的元素;如果元素可能不存在,應先檢查。
11.4 更新與移除資料
為 dataset 屬性賦值,會更新對應的 HTML 屬性:
const card = document.getElementById("course-card");
if (card) {
card.dataset.level = "intermediate";
card.dataset.durationHours = "5";
}更新後的屬性包括:
data-level="intermediate"
data-duration-hours="5"賦值時,資料會轉換成字串:
card.dataset.durationHours = 5; // 儲存為 "5"。
card.dataset.note = null; // 儲存為 "null",不是移除屬性。使用 delete 移除資料屬性:
delete card.dataset.note;也可以使用一般屬性方法:
card.setAttribute("data-level", "beginner");
card.removeAttribute("data-level");修改資料屬性不會自動更新可見文字,也不會自動向輔助技術宣告變更。應用程式需要按需要實作這些行為。
11.5 空值與不存在的屬性
資料屬性可以存在,但值為空:
<div id="example" data-ready=""></div>讀取時會得到空字串:
const example = document.getElementById("example");
console.log(example.dataset.ready); // ""
console.log(example.hasAttribute("data-ready")); // true如果你的慣例是以屬性是否存在作為旗標,應明確檢查存在與否:
const ready = example.hasAttribute("data-ready");不要依靠值的真假判斷:
if (example.dataset.ready) {
// 當值為空字串時,不會執行。
}自訂資料屬性不是原生 HTML 布林屬性。例如,data-ready="false" 仍然存在,而且包含字串 "false"。
11.6 按資料屬性選取元素
CSS 和 JavaScript 都可以使用屬性選擇器。
選取具有某個屬性的元素
[data-featured] {
border: 2px solid #005ea8;
}這會符合所有具有 data-featured 的元素,包括值為 "false" 的元素。
選取具有特定值的元素
[data-featured="true"] {
border: 2px solid #005ea8;
}JavaScript 使用相同的選擇器語法:
const beginnerCourses = document.querySelectorAll(
'[data-level="beginner"]'
);一般自訂資料屬性的值比對會區分大小寫,除非你明確要求不區分:
<article data-level="Beginner">...</article>這不會符合 [data-level="beginner"] 選擇器。
一般用途的樣式通常使用 class 更清晰。當樣式取決於應用程式定義的資料值時,資料屬性選擇器便很有用。
11.7 資料屬性與其他屬性的分別
按用途選擇屬性:
| 需要 | 適合的屬性 |
|---|---|
| 可重用的樣式群組 | class |
| 唯一元素識別碼或片段目標 | id |
| 內容語言 | lang |
| 連結目的地 | href |
| 原生停用控制項 | disabled |
| 無障礙展開狀態 | 適當使用 aria-expanded |
| 自訂應用程式中繼資料 | data-* |
例如:
<button type="button" data-action="show-details">
顯示詳情
</button>data-action 可以幫助 JavaScript 識別預期操作,但不會實作該操作。
同樣地:
<button type="button" data-disabled="true">
報名
</button>這個按鈕沒有被原生停用。如果需要停用行為,應使用真正的 disabled 屬性。
不要使用私有資料慣例取代無障礙狀態:
<button
type="button"
aria-expanded="false"
aria-controls="course-details"
>
顯示課程詳情
</button>這裏的 aria-expanded 向輔助技術傳達相關狀態。單靠 data-expanded 不會做到這一點。
這些 ARIA 屬性本身也不會實作展開操作;程式仍需控制詳情區塊,並同步更新狀態。
11.8 讓重要資訊保持可見
資料屬性不會顯示為一般內容:
<article data-fee-hkd="300">
<h2>HTML 入門</h2>
</article>單靠這段標記,讀者看不到費用。
如果資訊對讀者重要,應將它寫在可見內容中:
<article data-fee-hkd="300">
<h2>HTML 入門</h2>
<p>費用:HK$300</p>
</article>如果同時維護可見內容及中繼資料,應確保它們一致。
CSS 可以透過產生的內容顯示屬性值:
.course-card::after {
content: attr(data-level);
}不過,CSS 產生的內容不能可靠地取代重要的文件文字。重要標籤、指引及描述應直接放在 HTML 內容中。
11.9 安全與私隱
資料屬性可以在瀏覽器中被檢查及修改。
不要將它們視為以下資料的可信儲存位置:
- 密碼。
- API 金鑰或存取權杖。
- 機密個人資料。
- 授權決策。
- 可信的價格或帳戶權限。
例如:
<button type="button" data-price-hkd="300">
選擇課程
</button>訪客可以修改這個屬性。處理交易之前,伺服器必須獨立核實課程及實際價格。
也不要將資料屬性值當作可信標記插入 innerHTML。如果預期輸出是純文字,應使用 textContent。
11.10 儲存較複雜的資料
資料屬性可以包含 JSON,但需要小心處理引號及跳脫:
<div
id="course-options"
data-options='{"level":"beginner","durationHours":3}'
></div>JavaScript 可以剖析它:
const element = document.getElementById("course-options");
if (element) {
try {
const options = JSON.parse(element.dataset.options);
console.log(options.level);
} catch {
console.error("課程設定不是有效的 JSON。");
}
}這個例子使用單引號包住 HTML 屬性值,讓 JSON 的雙引號可以出現在內部。
動態產生屬性值時,應使用正確的 HTML 屬性值跳脫處理。位置不當的引號可能會意外結束屬性。
成功剖析 JSON 不代表資料結構符合要求;應按用途驗證欄位及類型。
對大型或經常更新的資料結構,JavaScript 物件或適當的 JSON 資源,通常比長篇屬性值更容易維護。
11.11 語言與資料值
資料屬性不會設定內容語言:
<article data-level="beginner" lang="zh-Hant-HK">
<h2>HTML 入門</h2>
<p>適合初學者。</p>
</article>在這個例子中:
data-level是應用程式資料值。lang標示可見內容的語言。
通常可以保持 beginner 等內部值穩定,同時翻譯提供給讀者的標籤。
如果屬性值或可見文字包含中文或其他 Unicode 字元,應使用 UTF-8。
11.12 完整範例:篩選課程
以下中文版網頁使用 data-level 篩選課程卡片。課程程度也會顯示在可見內容中;停用 JavaScript 時,所有課程仍然可以閱讀。
id: hong-kong-html-data-attributes
name: 自訂資料屬性 — 課程篩選
type: html
content: |-
<!doctype html>
<html lang="zh-Hant-HK">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>自訂資料屬性 | 課程篩選</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
color: #222;
background: #f5f7fa;
font-family: system-ui, sans-serif;
line-height: 1.8;
}
main {
max-width: 46rem;
margin-inline: auto;
padding: 2rem 1rem;
}
h1,
h2 {
color: #005ea8;
line-height: 1.4;
}
.filter-panel {
margin-block: 1.5rem;
padding: 1rem;
border: 1px solid #767676;
border-radius: 0.5rem;
background: white;
}
label {
display: block;
margin-block-end: 0.5rem;
font-weight: 700;
}
select {
max-width: 100%;
padding: 0.5rem;
color: #222;
background: white;
border: 1px solid #767676;
border-radius: 0.25rem;
font: inherit;
}
.course-card {
margin-block: 1.5rem;
padding: 1.25rem;
border: 1px solid #767676;
border-radius: 0.5rem;
background: white;
}
.course-card h2 {
margin-block-start: 0;
}
.course-card > :last-child {
margin-block-end: 0;
}
.note {
padding: 1rem;
border-inline-start: 4px solid #005ea8;
background: #eaf2f8;
}
:focus-visible {
outline: 3px solid #005ea8;
outline-offset: 4px;
}
/* 即使日後加入其他 display 規則,也保持隱藏內容不顯示。 */
[hidden] {
display: none !important;
}
</style>
</head>
<body>
<main>
<h1>探索課程示例</h1>
<p>
按程度篩選虛構課程。
每張卡片都以資料屬性儲存課程程度。
</p>
<p class="note">
data 屬性供程式讀取。
讀者需要的課程程度也會顯示在頁面內容中。
</p>
<div id="filter-panel" class="filter-panel" hidden>
<label for="level-filter">按程度顯示課程</label>
<select id="level-filter">
<option value="all">所有程度</option>
<option value="beginner">入門</option>
<option value="intermediate">中級</option>
</select>
</div>
<p id="filter-status" role="status" aria-atomic="true">
顯示全部 3 個課程。
</p>
<div id="course-list">
<article
class="course-card"
data-course-id="html-basics"
data-level="beginner"
>
<h2>HTML 入門</h2>
<p>程度:入門</p>
<p>學習標題、段落、連結、清單及表格。</p>
</article>
<article
class="course-card"
data-course-id="css-basics"
data-level="beginner"
>
<h2>CSS 入門</h2>
<p>程度:入門</p>
<p>認識選擇器、文字樣式、間距及顏色。</p>
</article>
<article
class="course-card"
data-course-id="responsive-layout"
data-level="intermediate"
>
<h2>響應式版面</h2>
<p>程度:中級</p>
<p>使用 CSS Grid 及 Flexbox 建立可調整的版面。</p>
</article>
</div>
<noscript>
<p>
篩選功能需要 JavaScript。
上方已顯示所有課程。
</p>
</noscript>
</main>
<script>
const panel = document.getElementById("filter-panel");
const filter = document.getElementById("level-filter");
const status = document.getElementById("filter-status");
const cards = document.querySelectorAll(
"#course-list .course-card[data-level]"
);
function updateCourses() {
const selectedLevel = filter.value;
let visibleCount = 0;
cards.forEach((card) => {
const matches =
selectedLevel === "all" ||
card.dataset.level === selectedLevel;
card.hidden = !matches;
if (matches) {
visibleCount += 1;
}
});
status.textContent = `顯示 ${visibleCount} 個課程。`;
}
filter.addEventListener("change", updateCourses);
updateCourses();
// 完成篩選功能初始化後,才顯示控制項。
panel.hidden = false;
</script>
</body>
</html>在這個範例中:
data-level儲存篩選所用的值。dataset.level讀取該值。- 原生
hidden屬性控制每張卡片是否顯示。 - 可見文字標示每個課程的程度。
- 狀態區域向輔助技術傳達更新後的結果數量。
- 沒有 JavaScript 時,所有課程仍可閱讀,未啟用的篩選控制項則保持隱藏。
11.13 常見錯誤
| 錯誤 | 較好的做法 |
|---|---|
使用沒有 data- 前綴的自訂屬性 | 使用有效的自訂資料屬性 |
| 假設資料值是數字或布林值 | 明確轉換及驗證字串 |
將 "false" 當作布林假值 | 與約定值比較,例如 "true" |
| 混淆空值與不存在的屬性 | 檢查存在與否時使用 hasAttribute() |
將 data-course-id 讀取為 dataset.course-id | 使用 dataset.courseId |
以賦值 null 移除屬性 | 使用 delete 或 removeAttribute() |
只想符合 "true",卻使用 [data-featured] | 使用 [data-featured="true"] |
| 只將重要資訊放在中繼資料中 | 同時提供可見內容 |
| 用資料屬性取代原生或 ARIA 屬性 | 使用能提供預期行為或語意的屬性 |
| 信任可由用戶端修改的價格或權限 | 在伺服器驗證權威資料 |
| 在資料屬性中儲存機密 | 不要將機密放入傳送至用戶端的 HTML |
本章總結: 使用 data-* 儲存小量應用程式專用資訊,並透過 dataset 讀取及更新。記住屬性值是字串,並將可見內容、無障礙功能及安全責任分開處理。















