受け取り枠選択の一連の操作
日付選択、ラジオボタンの枠、選択不可の空き状況、動的な概要欄を備えた、独立したHTML/CSS/JavaScript。
<!doctype html>
<html lang="en">
<meta charset="utf-8" /><meta
name="viewport"
content="width=device-width,initial-scale=1"
/><title>Collection slot study</title
><style>
* {
box-sizing: border-box;
}
body {
margin: 0;
background: #fff;
color: #262626;
font:
16px/1.5 Arial,
sans-serif;
padding: 32px;
}
header {
border-top: 8px solid #f06c00;
padding-top: 24px;
display: flex;
justify-content: space-between;
gap: 24px;
}
h1 {
font-size: 38px;
line-height: 1.316;
margin: 8px 0 16px;
}
h2 {
font-size: 24px;
line-height: 1.333;
margin: 0 0 16px;
}
.eyebrow,
small {
font-size: 14px;
color: #404040;
}
p {
margin: 8px 0 24px;
}
.basket {
font-size: 48px;
line-height: 1;
background: #fef5f0;
border-radius: 50%;
width: 96px;
height: 96px;
display: grid;
place-items: center;
color: #7f0442;
}
main {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 32px;
margin-top: 24px;
}
fieldset {
border: 0;
padding: 0;
margin: 0 0 24px;
}
legend {
font-weight: bold;
margin-bottom: 16px;
}
.dates {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
button,
select {
font: inherit;
min-height: 48px;
padding: 12px 16px;
border: 1px solid #737373;
border-radius: 2px;
background: white;
color: #262626;
cursor: pointer;
}
.dates button[aria-pressed="true"] {
border: 2px solid #f06c00;
background: #fef5f0;
padding: 11px 15px;
}
.slots {
display: grid;
gap: 8px;
}
.slot {
display: flex;
gap: 12px;
align-items: center;
padding: 16px;
border: 1px solid #d8d8d8;
border-radius: 2px;
cursor: pointer;
}
.slot:has(input:checked) {
border: 2px solid #f06c00;
padding: 15px;
background: #fef5f0;
}
.slot:has(input:disabled) {
background: #f2f2f2;
cursor: default;
}
.slot input {
accent-color: #c15100;
width: 20px;
height: 20px;
}
.slot strong {
margin-left: auto;
}
.unavailable {
color: #404040;
}
.primary {
background: #f06c00;
border: 2px solid #f06c00;
font-weight: bold;
color: #000;
}
.primary:hover {
background: #e55000;
border-color: #e55000;
}
.primary:active {
background: #c15100;
border-color: #c15100;
}
.summary {
background: #fef5f0;
border-radius: 12px;
padding: 24px;
align-self: start;
}
.summary dl {
margin: 0;
}
.summary dt {
font-size: 14px;
margin-top: 16px;
}
.summary dd {
margin: 4px 0;
font-weight: bold;
}
#result {
padding: 16px;
background: #e6f4ed;
color: #246127;
margin-top: 24px;
}
button:focus-visible,
input:focus-visible {
outline: 3px solid #135ca3;
outline-offset: 3px;
}
footer {
border-top: 1px solid #d8d8d8;
margin-top: 32px;
padding-top: 16px;
font-size: 14px;
color: #404040;
}
[hidden] {
display: none !important;
}
@media (max-width: 767px) {
body {
padding: 16px;
}
main {
grid-template-columns: 1fr;
gap: 24px;
}
h1 {
font-size: 30px;
}
.basket {
display: none;
}
.dates button {
flex: 1;
}
.summary {
padding: 16px;
}
}
</style>
<body>
<header>
<div>
<span class="eyebrow">GROCERIES / ORIGINAL COLLECTION STUDY</span>
<h1>Choose a collection slot</h1>
<p>
Select a sample day and time. No order or real availability is
connected.
</p>
</div>
<div class="basket" aria-hidden="true">▥</div>
</header>
<main>
<section>
<fieldset>
<legend>Collection day</legend>
<div class="dates" id="dates"></div>
</fieldset>
<fieldset>
<legend id="slotLabel">Available times</legend>
<div id="slots" class="slots"></div>
</fieldset>
</section>
<aside class="summary">
<h2>Collection details</h2>
<dl>
<dt>Sample location</dt>
<dd>Riverside collection point</dd>
<dt>Day and time</dt>
<dd id="selection"></dd>
<dt>Sample fee</dt>
<dd id="fee"></dd>
</dl>
<p><small>All fees and slots are illustrative.</small></p>
<button class="primary" id="review">Review collection</button>
<div id="result" role="status" hidden></div>
</aside>
</main>
<footer>
Independent HTML interpretation of Sainsbury’s public guidance. Arial
fallback; no private Fable runtime or brand font assets.
</footer>
<script>
const days = [
{
label: "Monday",
slots: [
["09:00–10:00", 1, true],
["12:00–13:00", 0, true],
["17:00–18:00", 2, false],
],
},
{
label: "Tuesday",
slots: [
["09:00–10:00", 0, false],
["12:00–13:00", 1, true],
["17:00–18:00", 1, true],
],
},
{
label: "Wednesday",
slots: [
["09:00–10:00", 0, true],
["12:00–13:00", 0, true],
["17:00–18:00", 2, true],
],
},
];
let day = 0,
slot = 0;
const $ = (id) => document.getElementById(id);
function update() {
const choice = days[day].slots[slot];
$("selection").textContent = days[day].label + ", " + choice[0];
$("fee").textContent =
choice[1] === 0 ? "No collection fee" : "£" + choice[1].toFixed(2);
$("result").hidden = true;
}
function render() {
$("dates").replaceChildren(
...days.map((d, i) => {
let b = document.createElement("button");
b.textContent = d.label;
b.setAttribute("aria-pressed", i === day);
b.onclick = () => {
day = i;
slot = d.slots.findIndex((s) => s[2]);
render();
};
return b;
}),
);
$("slotLabel").textContent = days[day].label + " collection times";
$("slots").replaceChildren(
...days[day].slots.map((s, i) => {
let l = document.createElement("label");
l.className = "slot";
let input = document.createElement("input");
input.type = "radio";
input.name = "time";
input.checked = i === slot;
input.disabled = !s[2];
input.onchange = () => {
slot = i;
update();
};
let text = document.createElement("span");
text.textContent = s[0];
let fee = document.createElement("strong");
fee.textContent = s[2]
? s[1] === 0
? "No fee"
: "£" + s[1].toFixed(2)
: "Unavailable";
l.append(input, text, fee);
return l;
}),
);
update();
}
$("review").onclick = () => {
$("result").hidden = false;
$("result").textContent =
"Local selection ready: " +
$("selection").textContent +
". No slot has been reserved.";
};
render();
</script>
</body>
</html>
- 非公開パッケージや認証情報は不要です。


