يوضح هذا النمط كيفية إنشاء مكون إعدادات سريع الاستجابة ويدعم إدخالات متعددة للأجهزة ويعمل على المتصفحات.
المقالة الكاملة · الفيديو على YouTube · المصدر على جيت هب
HTML
<main> <h1>Settings</h1> <form> <section> <header> <h2>Sound & vibration</h2> <small>Adjust system volume channels</small> </header> <fieldset> <div class="fieldset-item"> <picture aria-hidden="true"> <svg viewBox="0 0 24 24"> <title>A note icon</title> <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/> </svg> </picture> <div class="input-stack"> <label for="media-volume" id="media-volume" aria-hidden="true"> Media volume </label> <input name="media-volume" aria-labelledby="media-volume" type="range" value="3" max="10" style="--track-fill: 30%" > </div> </div> <div class="fieldset-item"> <picture aria-hidden="true"> <title>A phone icon</title> <svg viewBox="0 0 24 24"> <path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z"/> </svg> </picture> <div class="input-stack"> <label for="call-volume" id="call-volume" aria-hidden="true">Call volume</label> <input name="call-volume" aria-labelledby="call-volume" type="range" value="7" max="10" style="--track-fill: 70%" > </div> </div> <div class="fieldset-item"> <picture aria-hidden="true"> <svg viewBox="0 0 24 24"> <title>A bell icon</title> <path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/> </svg> </picture> <div class="input-stack"> <label for="ring-volume" id="ring-volume" aria-hidden="true">Ring volume</label> <input name="ring-volume" aria-labelledby="ring-volume" type="range" value="5" max="10" style="--track-fill: 50%" > </div> </div> <div class="fieldset-item"> <picture aria-hidden="true"> <svg viewBox="0 0 24 24"> <title>An alarm clock icon</title> <path d="M22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM12.5 8H11v6l4.75 2.85.75-1.23-4-2.37V8zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"/> </svg> </picture> <div class="input-stack"> <label for="alarm-volume" id="alarm-volume" aria-hidden="true">Alarm volume</label> <input name="alarm-volume" aria-labelledby="alarm-volume" type="range" value="8" max="10" style="--track-fill: 80%" > </div> </div> </fieldset> </section> <section> <header> <h2>Notifications</h2> <small>Turn specific channels on/off</small> </header> <fieldset> <div class="fieldset-item"> <input type="checkbox" checked id="text-notifications" name="text-notifications" > <div class="input-stack"> <label for="text-notifications"> <h3>Text Messages</h3> <small>Get notified about all text messages sent to your device</small> </label> </div> </div> <div class="fieldset-item"> <input type="checkbox" id="voice-notifications" name="voice-notifications" > <div class="input-stack"> <label for="voice-notifications"> <h3>Voice Mail</h3> <small>Get notified about all voice messages sent to your device</small> </label> </div> </div> <div class="fieldset-item"> <input type="checkbox" id="email-notifications" name="email-notifications" > <div class="input-stack"> <label for="email-notifications"> <h3>Emails</h3> <small>Get notified about all text messages to your device</small> </label> </div> </div> </fieldset> </section> </form> </main>
CSS
@custom-media --motionOK (prefers-reduced-motion: no-preference); :root { --surface1: lch(10 0 0); --surface2: lch(15 0 0); --surface3: lch(20 0 0); --surface4: lch(25 0 0); --text1: lch(95 0 0); --text2: lch(75 0 0); --brand: lch(64 20 237); --brand-bg1: lch(70 64 349); --brand-bg2: lch(60 84 300); --brand-bg-gradient: linear-gradient( to bottom, var(--brand-bg1), var(--brand-bg2) ); --thumb-highlight-color: lch(100 0 0 / 20%); --space-xxs: .25rem; --space-xs: .5rem; --space-sm: 1rem; --space-md: 1.5rem; --space-lg: 2rem; --space-xl: 3rem; --space-xxl: 6rem; --isLTR: 1; --isRTL: -1; &:dir(rtl) { --isLTR: -1; --isRTL: 1; } @media (prefers-color-scheme: light) { & { --surface1: lch(90 0 0); --surface2: lch(100 0 0); --surface3: lch(98 0 0); --surface4: lch(85 0 0); --text1: lch(20 0 0); --text2: lch(40 0 0); --brand: lch(64 40 237); --brand-bg1: lch(50 64 349); --brand-bg2: lch(40 84 300); --thumb-highlight-color: lch(0 0 0 / 20%); } } } html { block-size: 100%; inline-size: 100%; } body { min-block-size: 100%; min-inline-size: 100%; box-sizing: border-box; margin: 0; padding-block: var(--space-xs); background: var(--surface1); color: var(--text1); font-family: system-ui, sans-serif; } h1,h2,h3 { margin: 0; font-weight: 500; } main { display: grid; gap: var(--space-xl); place-content: center; padding: var(--space-sm); @media (width >= 540px) { padding: var(--space-lg); } @media (width >= 800px) { padding: var(--space-xl); } } form { max-width: 89vw; display: grid; gap: var(--space-xl) var(--space-xxl); --repeat: auto-fit; grid-template-columns: repeat(var(--repeat), minmax(min(10ch, 100%), 35ch)); align-items: flex-start; @media (orientation: landscape) and (width >= 640px) { --repeat: 2; } } section { display: grid; gap: var(--space-md); } header { display: grid; gap: var(--space-xxs); } fieldset { border: 1px solid var(--surface4); background: var(--surface4); padding: 0; margin: 0; display: grid; gap: 1px; border-radius: var(--space-sm); overflow: hidden; transition: box-shadow .3s ease; &:focus-within { box-shadow: 0 5px 20px -10px hsl(0 0% 0% / 50%); } } input[type="range"] { --track-height: .5ex; --track-fill: 0%; --thumb-size: 3ex; --thumb-offset: -1.25ex; --thumb-highlight-size: 0px; display: block; inline-size: 100%; margin: 1ex 0; appearance: none; background: transparent; outline-offset: 5px; @media (hover: none) { --thumb-size: 30px; --thumb-offset: -14px; } &::-webkit-slider-runnable-track { appearance: none; block-size: var(--track-height); border-radius: 5ex; background: linear-gradient( to right, transparent var(--track-fill), var(--surface1) 0% ), var(--brand-bg-gradient) fixed; } &::-moz-range-track { appearance: none; block-size: var(--track-height); border-radius: 5ex; background: linear-gradient( to right, transparent var(--track-fill), var(--surface1) 0% ), var(--brand-bg-gradient) fixed; } &::-webkit-slider-thumb { appearance: none; cursor: ew-resize; border: 3px solid var(--surface3); block-size: var(--thumb-size); inline-size: var(--thumb-size); margin-block-start: var(--thumb-offset); border-radius: 50%; background: var(--brand-bg-gradient) fixed; box-shadow: 0 0 0 var(--thumb-highlight-size) var(--thumb-highlight-color); @media (--motionOK) { transition: box-shadow .1s ease; } @nest .fieldset-item:focus-within & { border-color: var(--surface2); } } &::-moz-range-thumb { appearance: none; cursor: ew-resize; border: 3px solid var(--surface3); block-size: var(--thumb-size); inline-size: var(--thumb-size); margin-block-start: var(--thumb-offset); border-radius: 50%; background: var(--brand-bg-gradient) fixed; box-shadow: 0 0 0 var(--thumb-highlight-size) var(--thumb-highlight-color); @media (--motionOK) { transition: box-shadow .1s ease; } @nest .fieldset-item:focus-within & { border-color: var(--surface2); } } &:is(:hover,:active) { --thumb-highlight-size: 10px; } } input[type="checkbox"] { inline-size: var(--space-sm); block-size: var(--space-sm); margin: 0; outline-offset: 5px; accent-color: var(--brand); position: relative; transform-style: preserve-3d; cursor: pointer; &:hover::before { --thumb-scale: 1; } @media (hover: none) { inline-size: var(--space-md); block-size: var(--space-md); } &::before { --thumb-scale: .01; --thumb-highlight-size: var(--space-xl); content: ""; inline-size: var(--thumb-highlight-size); block-size: var(--thumb-highlight-size); clip-path: circle(50%); position: absolute; inset-block-start: 50%; inset-inline-start: 50%; background: var(--thumb-highlight-color); transform-origin: center center; transform: translateX(calc(var(--isRTL) * 50%)) translateY(-50%) translateZ(-1px) scale(var(--thumb-scale)) ; will-change: transform; @media (--motionOK) { transition: transform .2s ease; } } } .fieldset-item { background: var(--surface3); transition: background .2s ease; display: grid; grid-template-columns: var(--space-lg) 1fr; gap: var(--space-md); padding-block: var(--space-sm); padding-inline: var(--space-md); @media (width >= 540px) { grid-template-columns: var(--space-xxl) 1fr; gap: var(--space-xs); padding-block: var(--space-md); padding-inline: 0 var(--space-xl); } &:focus-within { background: var(--surface2); & svg { fill: white; } & picture { clip-path: circle(50%); background: var(--brand-bg-gradient) fixed; } } & > :is(.input-stack, label) { display: grid; gap: var(--space-xs); } & > .input-stack > label { display: contents; } & > picture { block-size: var(--space-xl); inline-size: var(--space-xl); clip-path: circle(40%); display: inline-grid; place-content: center; background: var(--surface3) fixed; @media (--motionOK) { transition: clip-path .3s ease; } } & svg { fill: var(--text2); block-size: var(--space-md); } & > :is(picture, input[type="checkbox"]) { place-self: center; } } small { color: var(--text2); line-height: 1.5; }
JS
const form = document.querySelector('form') const sliders = document.querySelectorAll('input[type="range"]') const rangeToPercent = slider => { const max = slider.getAttribute('max') || 10 const percent = slider.value / max * 100 return `${parseInt(percent)}%` } sliders.forEach(slider => { slider.style.setProperty('--track-fill', rangeToPercent(slider)) slider.addEventListener('input', e => { e.target.style.setProperty('--track-fill', rangeToPercent(e.target)) }) }) form.addEventListener('input', e => { const formData = Object.fromEntries(new FormData(form)) console.table(formData) })