Thực hành tốt nhất về HTML
HTML vững chắc là nền tảng của mọi trang web tuyệt vời. Những mẹo này giúp bạn viết mã đánh dấu có ngữ nghĩa, mạnh mẽ và sẵn sàng cho tương lai mà trình duyệt và trình đọc màn hình yêu thích.
Đặt thuộc tính width và height rõ ràng trên ảnh cho phép trình duyệt dành sẵn không gian chính xác trước khi ảnh tải, loại bỏ Cumulative Layout Shift (CLS) — một trong những Chỉ số Core Web Vitals của Google.
<!-- ❌ Tệ: trình duyệt không biết kích thước cho đến khi ảnh tải --> <img src="hero.jpg" alt="Ảnh hero"> <!-- ✅ Tốt: không gian được dành sẵn ngay lập tức --> <img src="hero.jpg" alt="Ảnh hero" width="1200" height="630" loading="lazy">
aspect-ratio: auto trong CSS và trình duyệt sẽ tự động tính toán tỷ lệ chính xác, ngay cả khi CSS thay đổi kích thước ảnh.Một phác thảo tài liệu được xây dựng với các tiêu đề được lồng đúng cách giúp cả công cụ tìm kiếm hiểu hệ thống phân cấp nội dung của bạn và người dùng trình đọc màn hình điều hướng trang hiệu quả.
<h1>Tiêu đề Trang (một cho mỗi trang)</h1> <h2>Phần Chính</h2> <h3>Phần phụ</h3> <h3>Một phần phụ khác</h3> <h2>Một Phần Chính Khác</h2>
Khi mở liên kết trong tab mới với target="_blank", trang được mở có thể truy cập trang của bạn qua window.opener. Thêm rel="noopener noreferrer" ngăn chặn lỗ hổng bảo mật này và cũng ngăn thông tin referrer bị gửi đi.
<!-- ❌ Dễ bị tấn công --> <a href="https://example.com" target="_blank">Truy cập</a> <!-- ✅ An toàn --> <a href="https://example.com" target="_blank" rel="noopener noreferrer">Truy cập</a>
Thuộc tính autocomplete cho trình duyệt và trình quản lý mật khẩu biết cách điền trước các trường biểu mẫu. Điều này cải thiện đáng kể tỷ lệ hoàn thành biểu mẫu trên di động.
<input type="text" autocomplete="given-name"> <input type="email" autocomplete="email"> <input type="tel" autocomplete="tel"> <input type="password" autocomplete="current-password"> <input type="text" autocomplete="postal-code">
Thẻ meta Open Graph kiểm soát cách trang của bạn trông như thế nào khi được chia sẻ trên các nền tảng mạng xã hội như Facebook, LinkedIn và Twitter/X. Nếu không có chúng, các nền tảng sẽ chọn nội dung ngẫu nhiên.
<meta property="og:title" content="Tiêu đề Trang"> <meta property="og:description" content="Mô tả ngắn"> <meta property="og:image" content="https://yourdomain.com/og.jpg"> <meta property="og:url" content="https://yourdomain.com/page"> <meta property="og:type" content="website"> <!-- Twitter/X --> <meta name="twitter:card" content="summary_large_image">
Mẹo & Thủ thuật CSS
CSS hiện đại cực kỳ mạnh mẽ. Những mẹo này bao gồm bố cục, thuộc tính tùy chỉnh và các mẫu giúp bạn tiết kiệm thời gian và giữ cho stylesheet của bạn sạch sẽ.
Đừng vật lộn với các hack định vị nữa. Viết tắt place-items của CSS Grid căn giữa nội dung theo cả chiều ngang và chiều dọc trong hai dòng — không cần calc, không cần transforms.
.center-everything { display: grid; place-items: center; } /* Hoạt động để căn giữa toàn trang */ body { display: grid; place-items: center; min-height: 100vh; }
Thuộc tính tùy chỉnh CSS (biến) cho phép bạn định nghĩa các token thiết kế một lần và tái sử dụng chúng ở mọi nơi. Chúng cũng cập nhật theo thời gian thực, làm cho chế độ tối và theming trở nên tầm thường.
:root { --color-primary: #6366f1; --color-bg: #ffffff; --radius-md: 12px; --spacing-md: 16px; } @media (prefers-color-scheme: dark) { :root { --color-bg: #0d1117; } } .btn { background: var(--color-primary); border-radius: var(--radius-md); padding: var(--spacing-md); }
clamp(min, preferred, max) cho phép kích thước phông chữ co giãn mượt mà với chiều rộng viewport — không cần media queries. Giá trị ở giữa thường là một đơn vị vw.
/* clamp(tối thiểu, ưu tiên, tối đa) */ h1 { font-size: clamp(1.8rem, 5vw, 3.5rem); } p { font-size: clamp(0.9rem, 2vw, 1.1rem); line-height: 1.7; max-width: 65ch; /* chiều rộng đọc tối ưu */ }
Bật cuộn mượt toàn cục và sử dụng scroll-margin-top trên các mục tiêu neo để đảm bảo header cố định không che khuất nội dung đang được cuộn đến.
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } } /* Bù đắp cho header cố định (ví dụ: cao 70px) */ [id] { scroll-margin-top: 90px; }
scroll-behavior: smooth trong media query prefers-reduced-motion: no-preference — một số người dùng bị say tàu xe do cuộn có hoạt ảnh.Mẫu auto-fill + minmax() tạo ra một lưới tự động điều chỉnh số cột dựa trên không gian có sẵn — không cần media queries.
.card-grid { display: grid; grid-template-columns: repeat( auto-fill, minmax(280px, 1fr) ); gap: 24px; } /* 1 cột trên di động → 2 → 3 → 4 tự động */
Đừng bao giờ làm outline: none toàn cục — nó phá vỡ điều hướng bàn phím. Sử dụng :focus-visible để chỉ hiển thị vòng focus cho người dùng bàn phím, giữ thiết kế sạch sẽ cho người dùng chuột.
/* ❌ Đừng bao giờ làm điều này */ * { outline: none; } /* ✅ Ẩn cho chuột, hiện cho bàn phím */ :focus:not(:focus-visible) { outline: none; } :focus-visible { outline: 2px solid #6366f1; outline-offset: 3px; border-radius: 4px; }
Mẹo JavaScript
Viết JavaScript sạch hơn, hiệu suất cao hơn với các mẫu hiện đại và API trình duyệt này.
Thay vì lắng nghe sự kiện scroll (kích hoạt hàng trăm lần mỗi giây), hãy sử dụng API IntersectionObserver để chỉ phản ứng khi các phần tử vào hoặc rời khỏi viewport.
const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); observer.unobserve(entry.target); // ngừng theo dõi } }); }, { rootMargin: '0px 0px -50px 0px' } ); document.querySelectorAll('.animate-on-scroll') .forEach(el => observer.observe(el));
Các sự kiện như resize và input kích hoạt rất nhanh. Debouncing trì hoãn việc thực thi cho đến khi người dùng ngừng tương tác, ngăn chặn công việc không cần thiết.
function debounce(fn, delay = 300) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; } // Cách sử dụng window.addEventListener('resize', debounce(() => { recalculateLayout(); }, 200) );
API navigator.clipboard hiện đại dựa trên promise và sạch sẽ hơn nhiều so với hack document.execCommand('copy') cũ.
async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); showToast('Đã sao chép!'); } catch (err) { console.error('Sao chép thất bại:', err); } } // Cách sử dụng document.querySelector('.copy-btn') .addEventListener('click', () => copyToClipboard('Văn bản cần sao chép') );
localStorage có thể ném lỗi trong chế độ duyệt web riêng tư hoặc khi bộ nhớ đầy. Luôn bọc nó trong một helper try/catch.
const storage = { get(key, fallback = null) { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : fallback; } catch { return fallback; } }, set(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); return true; } catch { return false; } } }; storage.set('theme', 'dark'); const theme = storage.get('theme', 'light');
Đối với các hoạt ảnh điều khiển bằng JavaScript (ví dụ: canvas, GSAP), hãy kiểm tra cài đặt prefers-reduced-motion của người dùng và đơn giản hóa hoặc vô hiệu hóa hoạt ảnh cho phù hợp.
const prefersReduced = window .matchMedia('(prefers-reduced-motion: reduce)') .matches; if (!prefersReduced) { startAnimations(); } else { showStaticFallback(); }
Mẹo về Hiệu suất
Trang web nhanh xếp hạng cao hơn, chuyển đổi tốt hơn và giữ chân người dùng hạnh phúc. Những mẹo này bao gồm những cải thiện lớn nhất cho hiệu suất web.
Nếu không có font-display: swap, trình duyệt sẽ ẩn văn bản cho đến khi phông chữ tùy chỉnh tải (FOIT). Với nó, phông chữ hệ thống được hiển thị ngay lập tức, sau đó được hoán đổi — cải thiện đáng kể Largest Contentful Paint (LCP).
<!-- Bước 1: preconnect --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <!-- Bước 2: thêm &display=swap vào URL --> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&family=Be+Vietnam+Pro:wght@700;900&display=swap" rel="stylesheet">
Ảnh WebP và AVIF nhỏ hơn đáng kể so với JPEG và PNG với chất lượng tương đương. Sử dụng phần tử <picture> để phục vụ các định dạng hiện đại với JPEG dự phòng cho các trình duyệt cũ hơn.
<picture> <source srcset="hero.avif" type="image/avif"> <source srcset="hero.webp" type="image/webp"> <img src="hero.jpg" alt="Ảnh hero" width="1200" height="630" loading="lazy"> </picture>
Thẻ <script> thông thường chặn phân tích cú pháp HTML. defer chạy script sau khi phân tích cú pháp hoàn tất (theo thứ tự). async chạy script ngay khi chúng tải xuống (thứ tự không được đảm bảo).
<!-- Chặn kết xuất ❌ --> <script src="app.js"></script> <!-- Chạy theo thứ tự, sau DOM ✅ (sử dụng cho hầu hết script) --> <script src="app.js" defer></script> <!-- Chạy ngay khi sẵn sàng ✅ (analytics, ads) --> <script src="analytics.js" async></script>
Chỉ sử dụng hoạt ảnh transform và opacity — chúng chạy trên luồng tổng hợp GPU mà không kích hoạt lại bố cục hoặc vẽ lại. Thêm will-change chỉ khi bạn lập hồ sơ và thấy lợi ích thực sự.
/* ❌ Kích hoạt bố cục (tốn kém) */ .bad { transition: width 0.3s, top 0.3s; } /* ✅ Tổng hợp GPU (rẻ) */ .good { transition: transform 0.3s, opacity 0.3s; } /* Sử dụng tiết kiệm, chỉ trước các hoạt ảnh phức tạp */ .modal-enter { will-change: transform; } .modal-entered { will-change: auto; } /* đặt lại sau */
Sử dụng <link rel="preload"> để yêu cầu trình duyệt tìm nạp các tài nguyên quan trọng sớm — trước khi nó phát hiện ra chúng trong CSS hoặc JS. Lý tưởng cho ảnh hero, phông chữ quan trọng và script chính.
<!-- Tải trước ảnh hero (phần tử LCP) --> <link rel="preload" as="image" href="hero.webp" fetchpriority="high"> <!-- Tải trước phông chữ quan trọng --> <link rel="preload" as="font" href="font.woff2" type="font/woff2" crossorigin>
Mẹo về Khả năng Truy cập
Khả năng truy cập không phải là một danh sách kiểm tra — đó là thiết kế tốt. Những mẹo này giúp làm cho trang web của bạn có thể sử dụng được bởi tất cả mọi người, bao gồm cả người dùng trình đọc màn hình và người điều hướng bằng bàn phím.
Các nút chỉ chứa một biểu tượng không có văn bản hiển thị để trình đọc màn hình thông báo. Thêm aria-label để cung cấp một nhãn mô tả.
<!-- ❌ Trình đọc màn hình nói "nút" --> <button><i class="fa-solid fa-times"></i></button> <!-- ✅ Trình đọc màn hình nói "Đóng hộp thoại" --> <button aria-label="Đóng hộp thoại"> <i class="fa-solid fa-times" aria-hidden="true"></i> </button>
Thuộc tính lang cho trình đọc màn hình biết nên sử dụng ngôn ngữ nào, cho phép ngắt dòng chính xác trong CSS và giúp các công cụ dịch xác định ngôn ngữ nội dung. Đối với các ngôn ngữ RTL, cũng thêm dir="rtl".
<!-- Tiếng Anh --> <html lang="en"> <!-- Tiếng Ả Rập (RTL) --> <html lang="ar" dir="rtl"> <!-- Tiếng Pháp --> <html lang="fr">
WCAG 2.1 AA yêu cầu tỷ lệ tương phản ít nhất 4.5:1 cho văn bản thông thường và 3:1 cho văn bản lớn (18px+ đậm hoặc 24px+ thường). Sử dụng công cụ kiểm tra độ tương phản trước khi xuất bản.
/* ❌ #6b7280 trên #fff = 4.48:1 (không đạt AA cho văn bản nhỏ) */ .muted { color: #6b7280; background: #fff; } /* ✅ #4b5563 trên #fff = 7.0:1 (đạt AA & AAA) */ .muted { color: #4b5563; background: #fff; }
Đôi khi bạn cần văn bản chỉ trình đọc màn hình mới có thể nghe thấy — như liên kết bỏ qua đến nội dung hoặc nhãn cho một phần tử trực quan. Lớp tiện ích .sr-only ẩn nó về mặt trực quan trong khi vẫn giữ nó có thể truy cập được.
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } <!-- Ví dụ liên kết bỏ qua --> <a href="#main" class="sr-only">Bỏ qua nội dung</a>
Khi một modal mở, focus bàn phím phải được giới hạn bên trong nó. Nếu không, Tab sẽ focus vào các phần tử phía sau lớp phủ — làm cho modal không thể sử dụng được đối với người dùng bàn phím.
function trapFocus(modal) { const focusable = modal.querySelectorAll( 'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])' ); const first = focusable[0]; const last = focusable[focusable.length - 1]; modal.addEventListener('keydown', e => { if (e.key !== 'Tab') return; if (e.shiftKey ? document.activeElement === first : document.activeElement === last) { e.preventDefault(); (e.shiftKey ? last : first).focus(); } }); first.focus(); }
Mẹo về Responsive & Di động
Lưu lượng truy cập di động chiếm ưu thế. Những mẹo này đảm bảo thiết kế của bạn hoạt động đẹp mắt trên mọi kích thước màn hình — từ điện thoại 320px đến màn hình 4K.
Nếu không có thẻ meta viewport, trình duyệt di động hiển thị ở chiều rộng máy tính ~980px và sau đó thu nhỏ. Cho phép người dùng thu phóng — vô hiệu hóa nó là một rào cản lớn về khả năng truy cập và phá vỡ thu phóng trình duyệt.
<!-- ❌ Vô hiệu hóa thu phóng người dùng (vi phạm khả năng truy cập) --> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <!-- ✅ Chính xác: cho phép thu phóng lên đến 5× --> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5">
Hướng dẫn Giao diện Người dùng của Apple và WCAG 2.5.5 khuyến nghị mục tiêu chạm tối thiểu 44×44 pixel CSS. Các mục tiêu nhỏ gây khó chịu cho người dùng và dẫn đến chạm sai. Bạn có thể mở rộng khu vực có thể nhấp mà không thay đổi kích thước trực quan bằng cách sử dụng padding hoặc pseudo-elements.
/* Mở rộng khu vực chạm mà không thay đổi trực quan */ .icon-btn { position: relative; width: 24px; height: 24px; } .icon-btn::before { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-width: 44px; min-height: 44px; }
Các thuộc tính CSS logic như margin-inline-start thay vì margin-left tự động lật hướng dựa trên chế độ viết của tài liệu — làm cho hỗ trợ RTL gần như không tốn công sức.
/* Vật lý (không lật cho RTL) */ .icon { margin-right: 8px; } /* Logic (tự động lật trong RTL) */ .icon { margin-inline-end: 8px; } /* Thêm ví dụ thuộc tính logic */ .card { padding-inline: 16px; /* trái + phải */ padding-block: 12px; /* trên + dưới */ border-start-start-radius: 12px; /* trên-trái trong LTR */ }
100vh trên trình duyệt di động bao gồm cả chrome của trình duyệt (thanh địa chỉ), gây tràn khi thanh xuất hiện. Đơn vị dvh (chiều cao viewport động) mới điều chỉnh khi chrome trình duyệt hiện hoặc ẩn.
/* ❌ Tràn trên di động khi chrome trình duyệt xuất hiện */ .hero { height: 100vh; } /* ✅ Với dự phòng cho các trình duyệt cũ hơn */ .hero { height: 100vh; /* dự phòng */ height: 100dvh; /* chiều cao viewport động */ }
svh (viewport nhỏ, luôn loại trừ chrome) và lvh (viewport lớn, luôn bao gồm không gian chrome).Thêm từ ResponsiveCheckTool
Áp Dụng Những Mẹo Này Vào Thực Tế
Kiểm tra xem các thay đổi responsive của bạn trông như thế nào trên điện thoại, máy tính bảng và máy tính — ngay lập tức, miễn phí, không cần đăng ký.
Kiểm Tra Website Của Bạn Ngay