1. Nâng cao trải nghiệm nhập liệu với Radio và Checkbox
Trong phát triển giao diện, việc cải thiện trạng thái focus cho các nút chọn giúp người dùng dễ dàng nhận biết vị trí hiện tại. Dưới đây là cách sử dụng CSS pseudo-class :focus-within để thay đổi style của label khi input bên trong được chọn, kèm theo chức năng "Chọn tất cả" bằng JavaScript.
Xem mã nguồn demo
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Demo Form Focus & Select All</title>
<style>
.control-panel label {
display: inline-block;
margin: 10px 15px 10px 0;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 15px;
transition: background 0.3s, color 0.3s;
}
/* Đổi màu label khi input bên trong được focus */
.control-panel label:focus-within {
color: #0056b3;
font-weight: 700;
background-color: #e6f0ff;
}
.control-panel input:focus {
outline: 2px solid #0056b3;
outline-offset: 1px;
}
body {
font-family: Arial, sans-serif;
padding: 30px;
background: #f4f4f4;
}
.wrapper {
background: #fff;
padding: 25px;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
max-width: 600px;
}
.divider {
margin: 25px 0;
border: 0;
border-top: 1px solid #ddd;
}
.master-row {
margin-bottom: 15px;
padding: 8px 0;
border-bottom: 1px dashed #ccc;
}
.master-row label {
font-weight: 700;
color: #222;
}
</style>
</head>
<body>
<div class="wrapper">
<h3>Tương tác Focus & Chọn nhóm</h3>
<!-- Nhóm Radio -->
<div class="control-panel">
<p><strong>Chọn giới tính:</strong></p>
<label>
<input type="radio" name="sex" value="male"> Nam
</label>
<label>
<input type="radio" name="sex" value="female"> Nữ
</label>
</div>
<hr class="divider">
<!-- Nhóm Checkbox -->
<div class="control-panel">
<p><strong>Sở thích:</strong></p>
<div class="master-row">
<label>
<input type="checkbox" id="masterSwitch"> ✅ Chọn tất cả
</label>
</div>
<label>
<input type="checkbox" class="choice-item" value="read"> Đọc sách
</label>
<label>
<input type="checkbox" class="choice-item" value="sport"> Thể thao
</label>
<label>
<input type="checkbox" class="choice-item" value="music"> Âm nhạc
</label>
<label>
<input type="checkbox" class="choice-item" value="travel"> Du lịch
</label>
</div>
</div>
<script>
(function() {
const masterCheck = document.getElementById('masterSwitch');
const options = document.querySelectorAll('.choice-item');
// Cập nhật trạng thái nút chủ dựa trên các nút con
function syncMasterState() {
const allChecked = Array.from(options).every(item => item.checked);
masterCheck.checked = allChecked;
}
// Khi nút chủ thay đổi
masterCheck.addEventListener('change', function() {
options.forEach(item => {
item.checked = masterCheck.checked;
});
syncMasterState();
});
// Khi các nút con thay đổi
options.forEach(item => {
item.addEventListener('change', syncMasterState);
});
// Khởi tạo trạng thái ban đầu
syncMasterState();
})();
</script>
</body>
</html>
2. Mô phỏng và điều khiển Flexbox Layout
Flexbox là công cụ mạnh mẽ để bố cục giao diện. Để hiểu rõ cách các thuộc tính hoạt động, chúng ta có thể xây dựng một bảng điều khiển cho phép thay đổi các tham số của container và items theo thời gian thực.
Xem mã nguồn demo
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Flexbox Interactive Lab</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: sans-serif;
background: #eef2f5;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.workspace {
max-width: 1000px;
width: 100%;
background: #fff;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
padding: 30px;
}
h1 { font-size: 1.5rem; color: #333; margin-bottom: 5px; }
.desc { color: #666; font-size: 0.9rem; margin-bottom: 20px; border-left: 3px solid #ccc; padding-left: 10px; }
.display-area {
background: #f8f9fa;
border: 1px solid #ddd;
padding: 20px;
margin-bottom: 20px;
border-radius: 10px;
}
.flex-workspace {
display: flex;
background: #e2e8f0;
border-radius: 8px;
padding: 15px;
min-height: 200px;
gap: 10px;
border: 2px dashed #94a3b8;
flex-wrap: nowrap;
}
.layout-box {
background: #3b82f6;
color: #fff;
border-radius: 6px;
padding: 10px;
text-align: center;
min-width: 40px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9rem;
flex: 0 1 auto;
}
.layout-box:nth-child(1) { background: #2563eb; }
.layout-box:nth-child(2) { background: #3b82f6; }
.layout-box:nth-child(3) { background: #60a5fa; }
.layout-box:nth-child(4) { background: #93c5fd; }
.layout-box:nth-child(5) { background: #bfdbfe; color: #1e3a8a; }
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 15px;
background: #f1f5f9;
padding: 20px;
border-radius: 10px;
border: 1px solid #cbd5e1;
}
.setting-item { display: flex; flex-direction: column; gap: 5px; }
.setting-item label {
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
color: #475569;
}
.setting-item select, .setting-item input {
padding: 5px;
border-radius: 4px;
border: 1px solid #cbd5e1;
font-size: 0.85rem;
}
.item-settings {
margin-top: 20px;
display: flex;
flex-wrap: wrap;
gap: 15px;
background: #fff;
padding: 15px;
border-radius: 8px;
border: 1px solid #cbd5e1;
}
.item-settings .setting-item { flex: 1 0 120px; }
</style>
</head>
<body>
<div class="workspace">
<h1>🧪 Flexbox Interactive Lab</h1>
<div class="desc">Điều chỉnh thuộc tính để quan sát thay đổi bố cục</div>
<div class="display-area">
<div class="flex-workspace" id="layoutBox">
<div class="layout-box" id="box1">1</div>
<div class="layout-box" id="box2">2</div>
<div class="layout-box" id="box3">3</div>
<div class="layout-box" id="box4">4</div>
<div class="layout-box" id="box5">5</div>
</div>
</div>
<div class="settings-grid" id="containerSettings">
<div class="setting-item">
<label>Direction</label>
<select id="axisDirection">
<option value="row">row</option>
<option value="row-reverse">row-reverse</option>
<option value="column">column</option>
<option value="column-reverse">column-reverse</option>
</select>
</div>
<div class="setting-item">
<label>Wrap</label>
<select id="lineWrap">
<option value="nowrap">nowrap</option>
<option value="wrap">wrap</option>
<option value="wrap-reverse">wrap-reverse</option>
</select>
</div>
<div class="setting-item">
<label>Justify Content</label>
<select id="mainAlign">
<option value="flex-start">flex-start</option>
<option value="center">center</option>
<option value="space-between">space-between</option>
<option value="space-around">space-around</option>
</select>
</div>
<div class="setting-item">
<label>Align Items</label>
<select id="crossAlign">
<option value="stretch">stretch</option>
<option value="center">center</option>
<option value="flex-start">flex-start</option>
<option value="flex-end">flex-end</option>
</select>
</div>
<div class="setting-item">
<label>Gap <span id="gapDisplay">10px</span></label>
<input type="range" id="gapRange" min="0" max="30" step="5" value="10" />
</div>
</div>
<div class="item-settings">
<div class="setting-item">
<label>Box 1 Grow</label>
<select id="expand1"><option value="0">0</option><option value="1">1</option><option value="2">2</option></select>
</div>
<div class="setting-item">
<label>Box 2 Grow</label>
<select id="expand2"><option value="0">0</option><option value="1" selected>1</option><option value="2">2</option></select>
</div>
<div class="setting-item">
<label>Box 3 Grow</label>
<select id="expand3"><option value="0">0</option><option value="1">1</option><option value="2" selected>2</option></select>
</div>
<div class="setting-item">
<label>Box 3 Align Self</label>
<select id="selfAlign3">
<option value="auto">auto</option>
<option value="center">center</option>
<option value="flex-end">flex-end</option>
</select>
</div>
</div>
</div>
<script>
(function() {
const wrapper = document.getElementById('layoutBox');
const boxes = [
document.getElementById('box1'),
document.getElementById('box2'),
document.getElementById('box3'),
document.getElementById('box4'),
document.getElementById('box5')
];
// Controls
const dirCtrl = document.getElementById('axisDirection');
const wrapCtrl = document.getElementById('lineWrap');
const justifyCtrl = document.getElementById('mainAlign');
const alignCtrl = document.getElementById('crossAlign');
const gapCtrl = document.getElementById('gapRange');
const gapText = document.getElementById('gapDisplay');
const growCtrls = [
document.getElementById('expand1'),
document.getElementById('expand2'),
document.getElementById('expand3'),
// Box 4 & 5 default to 0 if not specified in HTML select, handled in logic
];
const selfAlignCtrl = document.getElementById('selfAlign3');
function renderLayout() {
// Container props
wrapper.style.flexDirection = dirCtrl.value;
wrapper.style.flexWrap = wrapCtrl.value;
wrapper.style.justifyContent = justifyCtrl.value;
wrapper.style.alignItems = alignCtrl.value;
const gapVal = gapCtrl.value;
wrapper.style.gap = gapVal + 'px';
gapText.textContent = gapVal + 'px';
// Item props
boxes.forEach((box, idx) => {
let growVal = 0;
if (growCtrls[idx]) {
growVal = growCtrls[idx].value;
}
box.style.flex = `${growVal} 1 auto`;
});
// Specific override for Box 3
boxes[2].style.alignSelf = selfAlignCtrl.value;
}
function initListeners() {
[dirCtrl, wrapCtrl, justifyCtrl, alignCtrl, gapCtrl].forEach(el => {
el.addEventListener('input', renderLayout);
});
[...growCtrls, selfAlignCtrl].forEach(el => {
el.addEventListener('change', renderLayout);
});
renderLayout();
}
initListeners();
})();
</script>
</body>
</html>