Pseudo-classes và Pseudo-elements

Pseudo-classes và Pseudo-elements

Pseudo-class (lớp giả) chọn phần tử theo trạng thái hoặc vị trí trong cây DOM.
Pseudo-element (phần tử giả) chọn phần của phần tử hoặc tạo nội dung ảo.


Pseudo-classes phổ biến

Tương tác người dùng

a:hover   { color: #ed7d31; }       /* Khi chuột di qua */
a:focus   { outline: 2px solid blue; } /* Khi được chọn (tab/click) */
a:active  { opacity: 0.7; }         /* Khi đang nhấn */
a:visited { color: purple; }        /* Đã từng truy cập */

Cấu trúc DOM

li:first-child   { font-weight: bold; }   /* Con đầu tiên */
li:last-child    { border-bottom: none; } /* Con cuối */
li:nth-child(2)  { color: red; }          /* Con thứ 2 */
li:nth-child(odd)  { background: #f5f5f5; }  /* Con lẻ */
li:nth-child(even) { background: white; }    /* Con chẵn */
li:nth-child(3n+1) { color: blue; }          /* 1, 4, 7, ... */

p:nth-of-type(2) { margin-top: 2rem; }   /* Phần tử <p> thứ 2 */
div:not(.special) { opacity: .5; }       /* Mọi div KHÔNG có .special */

Trạng thái form

input:checked  { /* checkbox/radio được chọn */ }
input:disabled { opacity: .4; cursor: not-allowed; }
input:focus    { border-color: #4472c4; outline: none; }
p:empty        { display: none; }

Pseudo-elements phổ biến

/* Thêm nội dung trước/sau phần tử */
.btn::before { content: "→ "; }
.req::after  { content: " *"; color: red; }

/* Trang trí dòng đầu và chữ đầu */
p::first-line   { font-weight: bold; }
p::first-letter { font-size: 2em; float: left; }

/* Placeholder trong input */
input::placeholder { color: #aaa; font-style: italic; }

/* Vùng chọn (selection) */
::selection { background: #ffd700; color: #333; }

Pseudo-elements dùng :: (hai dấu hai chấm) theo chuẩn CSS3. Một dấu : vẫn hoạt động nhưng không được khuyến khích.

Thuộc tính content

content chỉ dùng được với ::before::after:

.icon::before { content: "🔗"; }          /* Text/emoji */
.separator::after { content: " / "; }     /* Separator */
li::before { content: counter(item) ". "; } /* Counter */
.img::after { content: url('/icon.svg'); }  /* Ảnh */
blockquote::before { content: "\201C"; }    /* Unicode */

Demo: :hover, :focus, :active

Kết quả
<div class="demo-wrap">
  <h3>Trạng thái button</h3>
  <div class="btn-group">
    <button class="btn-state">Hover / Active</button>
    <button class="btn-state btn-focus-demo">Focus (Tab vào đây)</button>
    <button class="btn-state" disabled>Disabled</button>
  </div>

  <h3>Input states</h3>
  <form>
    <div class="form-row">
      <label>Email:</label>
      <input type="email" placeholder="Nhập email..." class="inp">
    </div>
    <div class="form-row">
      <label>Disabled:</label>
      <input type="text" value="Không thể nhập" disabled class="inp">
    </div>
    <div class="form-row">
      <label>
        <input type="checkbox" class="chk"> Đồng ý điều khoản
      </label>
    </div>
  </form>

  <h3>Link states</h3>
  <p>
    <a href="#visited-demo" class="demo-link">Link thường</a> &nbsp;
    <a href="https://example.com" class="demo-link" target="_blank">Link đã thăm (visited)</a>
  </p>
</div>
body { font-family: sans-serif; padding: 1rem; }
h3 { font-size: .9rem; color: #555; margin: 1rem 0 .4rem; }

.btn-group { display: flex; gap: 10px; flex-wrap: wrap; }

.btn-state {
  padding: 8px 18px; border: 2px solid #4472c4;
  background: #4472c4; color: white; border-radius: 6px;
  cursor: pointer; font-size: .9rem;
  transition: background .2s, transform .1s, box-shadow .2s;
}
.btn-state:hover  { background: #2a50a0; }
.btn-state:active { transform: scale(.97); background: #1a3070; }
.btn-state:focus  { outline: none; box-shadow: 0 0 0 3px rgba(68,114,196,.4); }
.btn-state:disabled { background: #aaa; border-color: #aaa; cursor: not-allowed; opacity: .6; }

.form-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.form-row label { min-width: 80px; font-size: .9rem; }
.inp {
  padding: 6px 10px; border: 2px solid #ccc; border-radius: 4px;
  font-size: .9rem; transition: border-color .2s, box-shadow .2s;
}
.inp:focus   { border-color: #4472c4; outline: none; box-shadow: 0 0 0 3px rgba(68,114,196,.2); }
.inp:disabled { background: #f5f5f5; color: #999; cursor: not-allowed; }
.inp:valid    { border-color: #28a745; }

.chk { width: 16px; height: 16px; cursor: pointer; accent-color: #4472c4; }

.demo-link { color: #4472c4; transition: color .2s; }
.demo-link:hover   { color: #ed7d31; text-decoration: underline; }
.demo-link:active  { color: #c00; }
.demo-link:visited { color: #7b2d8b; }

Demo: ::before::after

Kết quả
<div class="demo-wrap">
  <h3>Icon với ::before</h3>
  <ul class="icon-list">
    <li class="icon-check">Tính năng đã hoàn thành</li>
    <li class="icon-check">Hỗ trợ đa thiết bị</li>
    <li class="icon-star">Tính năng nổi bật</li>
    <li class="icon-error">Lỗi cần sửa</li>
  </ul>

  <h3>Breadcrumb separator với ::after</h3>
  <nav class="breadcrumb" aria-label="breadcrumb">
    <a href="#" class="bc-item">Trang chủ</a>
    <a href="#" class="bc-item">Web</a>
    <a href="#" class="bc-item">CSS</a>
    <span class="bc-item bc-current">Pseudo-elements</span>
  </nav>

  <h3>Trang trí với ::before và ::after</h3>
  <h2 class="decorated-title">Tiêu đề có trang trí</h2>

  <h3>Tooltip với ::after</h3>
  <p>Di chuột vào đây: <span class="tooltip" data-tip="Đây là tooltip bằng CSS thuần!">Văn bản có tooltip</span></p>
</div>
body { font-family: sans-serif; padding: 1rem; }
h3 { font-size: .9rem; color: #555; margin: 1rem 0 .4rem; }

/* Icon list */
.icon-list { list-style: none; padding: 0; margin: 0 0 .5rem; }
.icon-list li { padding: 4px 0 4px 24px; position: relative; }
.icon-check::before { content: "✓"; position: absolute; left: 0; color: #28a745; font-weight: bold; }
.icon-star::before  { content: "★"; position: absolute; left: 0; color: #e09a00; }
.icon-error::before { content: "✗"; position: absolute; left: 0; color: #dc3545; font-weight: bold; }

/* Breadcrumb */
.breadcrumb { display: flex; flex-wrap: wrap; gap: 0; font-size: .9rem; }
.bc-item { color: #4472c4; text-decoration: none; }
.bc-item::after { content: " / "; color: #999; padding: 0 4px; }
.bc-item:last-child::after { content: ""; }
.bc-current { color: #333; }

/* Decorated title */
.decorated-title {
  text-align: center; position: relative; font-size: 1.3rem;
  margin: .5rem 0; padding: 0 2rem;
}
.decorated-title::before,
.decorated-title::after {
  content: "—";
  color: #4472c4;
  position: absolute;
  top: 50%; transform: translateY(-50%);
}
.decorated-title::before { left: 0; }
.decorated-title::after  { right: 0; }

/* Tooltip */
.tooltip { position: relative; cursor: help; border-bottom: 1px dashed #4472c4; color: #4472c4; }
.tooltip::after {
  content: attr(data-tip);
  position: absolute; bottom: 110%; left: 50%; transform: translateX(-50%);
  background: #333; color: white; padding: 4px 10px; border-radius: 4px;
  white-space: nowrap; font-size: .8rem; pointer-events: none;
  opacity: 0; transition: opacity .2s;
  z-index: 10;
}
.tooltip:hover::after { opacity: 1; }

Demo: :nth-child:checked tùy chỉnh

Kết quả
<div class="demo-wrap">
  <h3>Zebra rows với :nth-child</h3>
  <table class="zebra-table">
    <thead>
      <tr><th>Tên</th><th>Môn học</th><th>Điểm</th></tr>
    </thead>
    <tbody>
      <tr><td>An</td><td>Toán</td><td>9.5</td></tr>
      <tr><td>Bình</td><td></td><td>8.0</td></tr>
      <tr><td>Chi</td><td>Hóa</td><td>9.0</td></tr>
      <tr><td>Dũng</td><td>Anh</td><td>7.5</td></tr>
      <tr><td>Em</td><td>Văn</td><td>8.5</td></tr>
    </tbody>
  </table>

  <h3>Custom checkbox với :checked</h3>
  <div class="checkbox-group">
    <label class="custom-check">
      <input type="checkbox">
      <span class="checkmark"></span>
      Lựa chọn đầu tiên
    </label>
    <label class="custom-check">
      <input type="checkbox" checked>
      <span class="checkmark"></span>
      Lựa chọn thứ hai (mặc định chọn)
    </label>
    <label class="custom-check">
      <input type="checkbox">
      <span class="checkmark"></span>
      Lựa chọn thứ ba
    </label>
  </div>
</div>
body { font-family: sans-serif; padding: 1rem; }
h3 { font-size: .9rem; color: #555; margin: 1rem 0 .4rem; }

/* Zebra table */
.zebra-table { border-collapse: collapse; width: 100%; margin-bottom: 1rem; }
.zebra-table th { background: #4472c4; color: white; padding: 8px 12px; text-align: left; }
.zebra-table td { padding: 7px 12px; border-bottom: 1px solid #e0e0e0; }
.zebra-table tbody tr:nth-child(even) { background: #f0f4ff; }
.zebra-table tbody tr:nth-child(odd)  { background: white; }
.zebra-table tbody tr:hover { background: #ddeeff; }
.zebra-table tbody tr:last-child td  { border-bottom: none; }
.zebra-table tbody tr:first-child td { font-weight: bold; }

/* Custom checkbox */
.checkbox-group { display: flex; flex-direction: column; gap: 10px; }
.custom-check {
  display: flex; align-items: center; gap: 10px;
  cursor: pointer; user-select: none; font-size: .95rem;
}
.custom-check input[type="checkbox"] { display: none; }

.checkmark {
  width: 20px; height: 20px; border: 2px solid #ccc;
  border-radius: 4px; background: white; flex-shrink: 0;
  transition: background .2s, border-color .2s;
  position: relative;
}
.checkmark::after {
  content: "";
  position: absolute;
  left: 5px; top: 2px;
  width: 6px; height: 10px;
  border: 2px solid white;
  border-left: none; border-top: none;
  transform: rotate(45deg);
  opacity: 0; transition: opacity .15s;
}
.custom-check input:checked + .checkmark { background: #4472c4; border-color: #4472c4; }
.custom-check input:checked + .checkmark::after { opacity: 1; }
.custom-check:hover .checkmark { border-color: #4472c4; }

Bình luận