HTML ๋ชจ๋ฒ ์ฌ๋ก
ํํํ HTML์ ๋ชจ๋ ํ๋ฅญํ ์น์ฌ์ดํธ์ ๊ธฐ์ด์ ๋๋ค. ์ด ํ๋ค์ ๋ธ๋ผ์ฐ์ ์ ์คํฌ๋ฆฐ ๋ฆฌ๋๊ฐ ์ ํธํ๋ ์๋ฏธ๋ก ์ ์ด๊ณ ๊ฒฌ๊ณ ํ๋ฉฐ ๋ฏธ๋ ์งํฅ์ ์ธ ๋งํฌ์ ์ ์์ฑํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
์ด๋ฏธ์ง์ ๋ช
์์ ์ธ width ๋ฐ height ์์ฑ์ ์ค์ ํ๋ฉด ์ด๋ฏธ์ง๊ฐ ๋ก๋๋๊ธฐ ์ ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์ฌ๋ฐ๋ฅธ ๊ณต๊ฐ์ ์์ฝํ์ฌ Google์ Core Web Vitals ์ค ํ๋์ธ ๋์ ๋ ์ด์์ ๋ณ๊ฒฝ(CLS)์ ์ ๊ฑฐํฉ๋๋ค.
<!-- โ ๋์จ: ๋ธ๋ผ์ฐ์ ๊ฐ ์ด๋ฏธ์ง ๋ก๋ ์ ๊น์ง ํฌ๊ธฐ๋ฅผ ๋ชจ๋ฆ --> <img src="hero.jpg" alt="ํ์ด๋ก ์ด๋ฏธ์ง"> <!-- โ ์ข์: ๊ณต๊ฐ ์ฆ์ ์์ฝ --> <img src="hero.jpg" alt="ํ์ด๋ก ์ด๋ฏธ์ง" width="1200" height="630" loading="lazy">
aspect-ratio: auto์ ํจ๊ป ์ฌ์ฉํ๋ฉด CSS๊ฐ ์ด๋ฏธ์ง ํฌ๊ธฐ๋ฅผ ์กฐ์ ํ ๋์๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์ฌ๋ฐ๋ฅธ ๋น์จ์ ์๋์ผ๋ก ๊ณ์ฐํฉ๋๋ค.์ ์ ํ๊ฒ ์ค์ฒฉ๋ ์ ๋ชฉ์ผ๋ก ๊ตฌ์ถ๋ ๋ฌธ์ ๊ฐ์๋ ๊ฒ์ ์์ง์ด ์ฝํ ์ธ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ์ดํดํ๊ณ ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์๊ฐ ํ์ด์ง๋ฅผ ํจ์จ์ ์ผ๋ก ํ์ํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
<h1>ํ์ด์ง ์ ๋ชฉ (ํ์ด์ง๋น ํ๋)</h1> <h2>์ฃผ์ ์น์ </h2> <h3>ํ์ ์น์ </h3> <h3>๋ค๋ฅธ ํ์ ์น์ </h3> <h2>๋ค๋ฅธ ์ฃผ์ ์น์ </h2>
target="_blank"๋ก ์ ํญ์์ ๋งํฌ๋ฅผ ์ด ๋, ์ด๋ฆฐ ํ์ด์ง๋ window.opener๋ฅผ ํตํด ๊ทํ์ ํ์ด์ง์ ์ ๊ทผํ ์ ์์ต๋๋ค. rel="noopener noreferrer"๋ฅผ ์ถ๊ฐํ๋ฉด ์ด ๋ณด์ ์ทจ์ฝ์ ์ ๋ฐฉ์งํ๊ณ ๋ฆฌํผ๋ฌ ์ ๋ณด๊ฐ ์ ์ก๋๋ ๊ฒ๋ ๋ง์ต๋๋ค.
<!-- โ ์ทจ์ฝํจ --> <a href="https://example.com" target="_blank">๋ฐฉ๋ฌธํ๊ธฐ</a> <!-- โ ์์ ํจ --> <a href="https://example.com" target="_blank" rel="noopener noreferrer">๋ฐฉ๋ฌธํ๊ธฐ</a>
autocomplete ์์ฑ์ ๋ธ๋ผ์ฐ์ ์ ๋น๋ฐ๋ฒํธ ๊ด๋ฆฌ์์๊ฒ ํผ ํ๋๋ฅผ ๋ฏธ๋ฆฌ ์ฑ์ฐ๋ ๋ฐฉ๋ฒ์ ์๋ ค์ค๋๋ค. ์ด๋ ๋ชจ๋ฐ์ผ ํผ ์๋ฃ์จ์ ๊ทน์ ์ผ๋ก ํฅ์์ํต๋๋ค.
<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">
Open Graph ๋ฉํ ํ๊ทธ๋ Facebook, LinkedIn, Twitter/X์ ๊ฐ์ ์์ ๋ฏธ๋์ด ํ๋ซํผ์์ ๊ณต์ ๋ ๋ ํ์ด์ง๊ฐ ์ด๋ป๊ฒ ํ์๋๋์ง ์ ์ดํฉ๋๋ค. ์ด ํ๊ทธ๊ฐ ์์ผ๋ฉด ํ๋ซํผ์์ ์์๋ก ์ฝํ ์ธ ๋ฅผ ์ ํํฉ๋๋ค.
<meta property="og:title" content="ํ์ด์ง ์ ๋ชฉ"> <meta property="og:description" content="์งง์ ์ค๋ช "> <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">
CSS ํ๊ณผ ์๋ น
์ต์ CSS๋ ๋งค์ฐ ๊ฐ๋ ฅํฉ๋๋ค. ์ด ํ๋ค์ ๋ ์ด์์, ์ฌ์ฉ์ ์ ์ ์์ฑ, ๊ทธ๋ฆฌ๊ณ ์๊ฐ์ ์ ์ฝํ๊ณ ์คํ์ผ์ํธ๋ฅผ ๊น๋ํ๊ฒ ์ ์งํ๋ ํจํด์ ๋ค๋ฃน๋๋ค.
์์น ์ง์ ํต๊ณผ ์ธ์ฐ์ง ๋ง์ธ์. CSS Grid์ place-items ๋จ์ถ ์์ฑ์ ์ฝํ
์ธ ๋ฅผ ๊ฐ๋ก ๋ฐ ์ธ๋ก๋ก ๋ ์ค๋ก ์ค์์ ๋ฐฐ์นํฉ๋๋ค. calc๋, transform๋ ํ์ ์์ต๋๋ค.
.center-everything { display: grid; place-items: center; } /* ์ ์ฒด ํ์ด์ง ์ค์ ๋ฐฐ์น์๋ ์๋ */ body { display: grid; place-items: center; min-height: 100vh; }
CSS ์ฌ์ฉ์ ์ ์ ์์ฑ(๋ณ์)์ ์ฌ์ฉํ๋ฉด ๋์์ธ ํ ํฐ์ ํ ๋ฒ ์ ์ํ๊ณ ๋ชจ๋ ๊ณณ์์ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ํ ์ค์๊ฐ์ผ๋ก ์ ๋ฐ์ดํธ๋์ด ๋คํฌ ๋ชจ๋ ๋ฐ ํ ๋ง ์ง์ ์ด ๊ฐ๋จํด์ง๋๋ค.
: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)๋ ๋ทฐํฌํธ ๋๋น์ ๋ฐ๋ผ ๊ธ๊ผด ํฌ๊ธฐ๊ฐ ๋ถ๋๋ฝ๊ฒ ์กฐ์ ๋๋๋ก ํฉ๋๋ค. ๋ฏธ๋์ด ์ฟผ๋ฆฌ๊ฐ ํ์ ์์ต๋๋ค. ์ค๊ฐ ๊ฐ์ ์ผ๋ฐ์ ์ผ๋ก vw ๋จ์์
๋๋ค.
/* clamp(์ต์, ์ ํธ, ์ต๋) */ 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; /* ์ต์ ์ฝ๊ธฐ ๋๋น */ }
์ ์ญ์ ์ผ๋ก ๋ถ๋๋ฌ์ด ์คํฌ๋กค์ ํ์ฑํํ๊ณ ์ต์ปค ๋์์ scroll-margin-top์ ์ฌ์ฉํ์ฌ ์คํฌ๋กค๋๋ ์ฝํ
์ธ ์์ ๊ณ ์ ํค๋๊ฐ ๊ฒน์น์ง ์๋๋ก ํ์ธ์.
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } } /* ๊ณ ์ ํค๋ ์คํ์ (์: 70px ๋์ด) */ [id] { scroll-margin-top: 90px; }
prefers-reduced-motion: no-preference ๋ฏธ๋์ด ์ฟผ๋ฆฌ ์์ scroll-behavior: smooth๋ฅผ ๋ํํ์ธ์. ์ผ๋ถ ์ฌ์ฉ์๋ ์ ๋๋ฉ์ด์
์คํฌ๋กค๋ก ์ธํด ๋ฉ๋ฏธ๋ฅผ ๊ฒฝํํ ์ ์์ต๋๋ค.auto-fill + minmax() ํจํด์ ์ฌ์ฉ ๊ฐ๋ฅํ ๊ณต๊ฐ์ ๋ฐ๋ผ ์ด ์๋ฅผ ์๋์ผ๋ก ์กฐ์ ํ๋ ๊ทธ๋ฆฌ๋๋ฅผ ๋ง๋ญ๋๋ค. ๋ฏธ๋์ด ์ฟผ๋ฆฌ๊ฐ ํ์ ์์ต๋๋ค.
.card-grid { display: grid; grid-template-columns: repeat( auto-fill, minmax(280px, 1fr) ); gap: 24px; } /* ๋ชจ๋ฐ์ผ์์ 1์ด โ 2 โ 3 โ 4์ด๋ก ์๋ ์กฐ์ */
์ ๋ ์ ์ญ์ ์ผ๋ก outline: none์ ํ์ง ๋ง์ธ์. ํค๋ณด๋ ํ์์ ๋ฐฉํดํฉ๋๋ค. :focus-visible์ ์ฌ์ฉํ์ฌ ํค๋ณด๋ ์ฌ์ฉ์์๊ฒ๋ง ํฌ์ปค์ค ๋ง์ ํ์ํ๊ณ ๋ง์ฐ์ค ์ฌ์ฉ์์๊ฒ๋ ๋์์ธ์ ๊น๋ํ๊ฒ ์ ์งํ์ธ์.
/* โ ์ ๋ ์ด๋ ๊ฒ ํ์ง ๋ง์ธ์ */ * { outline: none; } /* โ ๋ง์ฐ์ค์๋ ์จ๊ธฐ๊ณ ํค๋ณด๋์๋ ํ์ */ :focus:not(:focus-visible) { outline: none; } :focus-visible { outline: 2px solid #6366f1; outline-offset: 3px; border-radius: 4px; }
JavaScript ํ
์ด๋ฌํ ์ต์ ํจํด๊ณผ ๋ธ๋ผ์ฐ์ API๋ก ๋ ๊น๋ํ๊ณ ์ฑ๋ฅ์ด ๋ฐ์ด๋ JavaScript๋ฅผ ์์ฑํ์ธ์.
scroll ์ด๋ฒคํธ(์ด๋น ์๋ฐฑ ๋ฒ ๋ฐ์)๋ฅผ ์์ ํ๋ ๋์ , IntersectionObserver API๋ฅผ ์ฌ์ฉํ์ฌ ์์๊ฐ ๋ทฐํฌํธ์ ๋ค์ด์ค๊ฑฐ๋ ๋๊ฐ ๋๋ง ๋ฐ์ํ์ธ์.
const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); observer.unobserve(entry.target); // ๊ด์ฐฐ ์ค์ง } }); }, { rootMargin: '0px 0px -50px 0px' } ); document.querySelectorAll('.animate-on-scroll') .forEach(el => observer.observe(el));
resize ๋ฐ input๊ณผ ๊ฐ์ ์ด๋ฒคํธ๋ ๋งค์ฐ ๋น ๋ฅด๊ฒ ๋ฐ์ํฉ๋๋ค. ๋๋ฐ์ด์ฑ์ ์ฌ์ฉ์ ์ํธ์์ฉ์ด ๋ฉ์ถ ๋๊น์ง ์คํ์ ์ง์ฐ์์ผ ๋ถํ์ํ ์์
์ ๋ฐฉ์งํฉ๋๋ค.
function debounce(fn, delay = 300) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; } // ์ฌ์ฉ๋ฒ window.addEventListener('resize', debounce(() => { recalculateLayout(); }, 200) );
์ต์ navigator.clipboard API๋ ํ๋ก๋ฏธ์ค ๊ธฐ๋ฐ์ด๋ฉฐ ๊ธฐ์กด์ document.execCommand('copy') ํดํน๋ณด๋ค ํจ์ฌ ๊น๋ํฉ๋๋ค.
async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); showToast('๋ณต์ฌ๋จ!'); } catch (err) { console.error('๋ณต์ฌ ์คํจ:', err); } } // ์ฌ์ฉ๋ฒ document.querySelector('.copy-btn') .addEventListener('click', () => copyToClipboard('๋ณต์ฌํ ํ ์คํธ') );
localStorage๋ ๊ฐ์ธ์ ๋ณด ๋ณดํธ ๋ธ๋ผ์ฐ์ง ๋ชจ๋๋ ์ ์ฅ ๊ณต๊ฐ์ด ๊ฐ๋ ์ฐผ์ ๋ ์์ธ๋ฅผ ๋ฐ์์ํฌ ์ ์์ต๋๋ค. ํญ์ 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');
JavaScript ๊ธฐ๋ฐ ์ ๋๋ฉ์ด์
(์: canvas, GSAP)์ ๊ฒฝ์ฐ ์ฌ์ฉ์์ prefers-reduced-motion ์ค์ ์ ํ์ธํ๊ณ ๊ทธ์ ๋ฐ๋ผ ์ ๋๋ฉ์ด์
์ ๋จ์ํํ๊ฑฐ๋ ๋นํ์ฑํํ์ธ์.
const prefersReduced = window .matchMedia('(prefers-reduced-motion: reduce)') .matches; if (!prefersReduced) { startAnimations(); } else { showStaticFallback(); }
์ฑ๋ฅ ํ
๋น ๋ฅธ ์น์ฌ์ดํธ๋ ๋ ๋์ ์์๋ฅผ ๊ธฐ๋กํ๊ณ , ๋ ์ ์ ํ๋๋ฉฐ, ์ฌ์ฉ์๋ฅผ ํ๋ณตํ๊ฒ ๋ง๋ญ๋๋ค. ์ด ํ๋ค์ ์น ์ฑ๋ฅ์์ ๊ฐ์ฅ ํฐ ํจ๊ณผ๋ฅผ ๋ณผ ์ ์๋ ๋ถ๋ถ์ ๋ค๋ฃน๋๋ค.
font-display: swap์ด ์์ผ๋ฉด ๋ธ๋ผ์ฐ์ ๋ ์ฌ์ฉ์ ์ ์ ํฐํธ๊ฐ ๋ก๋๋ ๋๊น์ง ํ
์คํธ๋ฅผ ์จ๊น๋๋ค(FOIT). ์ด ์์ฑ์ ์ฌ์ฉํ๋ฉด ์์คํ
ํฐํธ๊ฐ ์ฆ์ ํ์๋ ๋ค์ ๊ต์ฒด๋์ด Largest Contentful Paint(LCP)๊ฐ ํฌ๊ฒ ํฅ์๋ฉ๋๋ค.
<!-- 1๋จ๊ณ: preconnect --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <!-- 2๋จ๊ณ: URL์ &display=swap ์ถ๊ฐ --> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&family=Noto+Sans+KR:wght@700;900&display=swap" rel="stylesheet">
WebP ๋ฐ AVIF ์ด๋ฏธ์ง๋ ๋์ผํ ํ์ง์ JPEG ๋ฐ PNG๋ณด๋ค ํจ์ฌ ์์ต๋๋ค. <picture> ์์๋ฅผ ์ฌ์ฉํ์ฌ ์ต์ ํ์์ ์ ๊ณตํ๊ณ ๊ตฌํ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ํด JPEG ํด๋ฐฑ์ ์ ๊ณตํ์ธ์.
<picture> <source srcset="hero.avif" type="image/avif"> <source srcset="hero.webp" type="image/webp"> <img src="hero.jpg" alt="ํ์ด๋ก ์ด๋ฏธ์ง" width="1200" height="630" loading="lazy"> </picture>
์ผ๋ฐ <script> ํ๊ทธ๋ HTML ํ์ฑ์ ์ฐจ๋จํฉ๋๋ค. defer๋ ํ์ฑ์ด ์๋ฃ๋ ํ ์์๋๋ก ์คํฌ๋ฆฝํธ๋ฅผ ์คํํฉ๋๋ค. async๋ ๋ค์ด๋ก๋๋๋ ์ฆ์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํฉ๋๋ค(์์ ๋ณด์ฅ ์ ๋จ).
<!-- ๋ ๋๋ง ์ฐจ๋จ โ --> <script src="app.js"></script> <!-- DOM ์ดํ ์์๋๋ก ์คํ โ (๋๋ถ๋ถ์ ์คํฌ๋ฆฝํธ์ ์ฌ์ฉ) --> <script src="app.js" defer></script> <!-- ์ค๋น๋๋ ์ฆ์ ์คํ โ (๋ถ์, ๊ด๊ณ ) --> <script src="analytics.js" async></script>
transform๊ณผ opacity๋ง ์ ๋๋ฉ์ด์
ํ์ธ์. ์ด๋ค์ ๋ ์ด์์์ด๋ ํ์ธํธ๋ฅผ ํธ๋ฆฌ๊ฑฐํ์ง ์๊ณ GPU ์ปดํฌ์งํฐ ์ค๋ ๋์์ ์คํ๋ฉ๋๋ค. will-change๋ ํ๋กํ์ผ๋ง ํ ์ค์ ์ด์ ์ด ์๋ ๊ฒฝ์ฐ์๋ง ์ถ๊ฐํ์ธ์.
/* โ ๋ ์ด์์ ํธ๋ฆฌ๊ฑฐ (๋น์ฉ ๋์) */ .bad { transition: width 0.3s, top 0.3s; } /* โ GPU ์ปดํฌ์งํธ (๋น์ฉ ๋ฎ์) */ .good { transition: transform 0.3s, opacity 0.3s; } /* ๋ณต์กํ ์ ๋๋ฉ์ด์ ์ ์๋ง ๋๋ฌผ๊ฒ ์ฌ์ฉ */ .modal-enter { will-change: transform; } .modal-entered { will-change: auto; } /* ์ดํ ์ด๊ธฐํ */
<link rel="preload">๋ฅผ ์ฌ์ฉํ์ฌ ๋ธ๋ผ์ฐ์ ๊ฐ CSS๋ JS์์ ๋ฐ๊ฒฌํ๊ธฐ ์ ์ ์ค์ ๋ฆฌ์์ค๋ฅผ ์ผ์ฐ ๊ฐ์ ธ์ค๋๋ก ์๋ฆฌ์ธ์. ํ์ด๋ก ์ด๋ฏธ์ง, ์ค์ ํฐํธ, ์ฃผ์ ์คํฌ๋ฆฝํธ์ ์ด์์ ์
๋๋ค.
<!-- ํ์ด๋ก ์ด๋ฏธ์ง ํ๋ฆฌ๋ก๋ (LCP ์์) --> <link rel="preload" as="image" href="hero.webp" fetchpriority="high"> <!-- ์ค์ ํฐํธ ํ๋ฆฌ๋ก๋ --> <link rel="preload" as="font" href="font.woff2" type="font/woff2" crossorigin>
์ ๊ทผ์ฑ ํ
์ ๊ทผ์ฑ์ ์ฒดํฌ๋ฆฌ์คํธ๊ฐ ์๋๋๋ค. ์ข์ ๋์์ธ์ ๋๋ค. ์ด ํ๋ค์ ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์์ ํค๋ณด๋ ํ์์๋ฅผ ํฌํจํ ๋ชจ๋ ์ฌ๋์ด ์ฌ์ดํธ๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ๋์์ค๋๋ค.
์์ด์ฝ๋ง ํฌํจ๋ ๋ฒํผ์๋ ์คํฌ๋ฆฐ ๋ฆฌ๋๊ฐ ์ฝ์ ์ ์๋ ํ
์คํธ๊ฐ ์์ต๋๋ค. aria-label์ ์ถ๊ฐํ์ฌ ์ค๋ช
๋ ์ด๋ธ์ ์ ๊ณตํ์ธ์.
<!-- โ ์คํฌ๋ฆฐ ๋ฆฌ๋: "๋ฒํผ" --> <button><i class="fa-solid fa-times"></i></button> <!-- โ ์คํฌ๋ฆฐ ๋ฆฌ๋: "๋ํ ์์ ๋ซ๊ธฐ" --> <button aria-label="๋ํ ์์ ๋ซ๊ธฐ"> <i class="fa-solid fa-times" aria-hidden="true"></i> </button>
lang ์์ฑ์ ์คํฌ๋ฆฐ ๋ฆฌ๋์ ์ฌ์ฉํ ์ธ์ด๋ฅผ ์๋ ค์ฃผ๊ณ , CSS์์ ์ฌ๋ฐ๋ฅธ ํ์ดํ ๋ฃ๊ธฐ๋ฅผ ํ์ฑํํ๋ฉฐ, ๋ฒ์ญ ๋๊ตฌ๊ฐ ์ฝํ
์ธ ์ธ์ด๋ฅผ ์๋ณํ๋๋ก ๋์ต๋๋ค. RTL ์ธ์ด์ ๊ฒฝ์ฐ dir="rtl"๋ ์ถ๊ฐํ์ธ์.
<!-- ํ๊ตญ์ด --> <html lang="ko"> <!-- ์๋์ด (RTL) --> <html lang="ar" dir="rtl"> <!-- ํ๋์ค์ด --> <html lang="fr">
WCAG 2.1 AA๋ ์ผ๋ฐ ํ ์คํธ์ ๊ฒฝ์ฐ ์ต์ 4.5:1, ํฐ ํ ์คํธ(18px+ ๊ตต๊ฒ ๋๋ 24px+ ์ผ๋ฐ)์ ๊ฒฝ์ฐ 3:1์ ๋ช ์๋น๋ฅผ ์๊ตฌํฉ๋๋ค. ์ถ์ ์ ์ ๋ช ์๋น ๊ฒ์ฌ๊ธฐ๋ฅผ ์ฌ์ฉํ์ธ์.
/* โ #fff ์ #6b7280 = 4.48:1 (์์ ํ ์คํธ AA ์คํจ) */ .muted { color: #6b7280; background: #fff; } /* โ #fff ์ #4b5563 = 7.0:1 (AA & AAA ํต๊ณผ) */ .muted { color: #4b5563; background: #fff; }
๋๋ก๋ ์คํฌ๋ฆฐ ๋ฆฌ๋๋ง ๋ค์ ์ ์๋ ํ
์คํธ๊ฐ ํ์ํฉ๋๋ค(์: ์ฝํ
์ธ ๋ก ๊ฑด๋๋ฐ๊ธฐ ๋งํฌ ๋๋ ์๊ฐ์ ์์์ ๋ ์ด๋ธ). .sr-only ์ ํธ๋ฆฌํฐ ํด๋์ค๋ ์ ๊ทผ์ฑ์ ์ ์งํ๋ฉด์ ์๊ฐ์ ์ผ๋ก ์จ๊น๋๋ค.
.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; } <!-- ๊ฑด๋๋ฐ๊ธฐ ๋งํฌ ์์ --> <a href="#main" class="sr-only">์ฝํ ์ธ ๋ก ๊ฑด๋๋ฐ๊ธฐ</a>
๋ชจ๋ฌ์ด ์ด๋ ค ์์ ๋ ํค๋ณด๋ ํฌ์ปค์ค๋ ๋ชจ๋ฌ ๋ด๋ถ์ ์ ํ๋์ด์ผ ํฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด Tab ํค๊ฐ ์ค๋ฒ๋ ์ด ๋ค์ ์์์ ํฌ์ปค์ค๋ฅผ ๋ง์ถฐ ํค๋ณด๋ ์ฌ์ฉ์๊ฐ ๋ชจ๋ฌ์ ์ฌ์ฉํ ์ ์๊ฒ ๋ฉ๋๋ค.
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(); }
๋ฐ์ํ ๋ฐ ๋ชจ๋ฐ์ผ ํ
๋ชจ๋ฐ์ผ ํธ๋ํฝ์ด ์ง๋ฐฐ์ ์ ๋๋ค. ์ด ํ๋ค์ 320px ํด๋ํฐ์์ 4K ๋ชจ๋ํฐ์ ์ด๋ฅด๊ธฐ๊น์ง ๋ชจ๋ ํ๋ฉด ํฌ๊ธฐ์์ ๋์์ธ์ด ์๋ฆ๋ต๊ฒ ์๋ํ๋๋ก ๋ณด์ฅํฉ๋๋ค.
๋ทฐํฌํธ ๋ฉํ ํ๊ทธ๊ฐ ์์ผ๋ฉด ๋ชจ๋ฐ์ผ ๋ธ๋ผ์ฐ์ ๋ ~980px ๋ฐ์คํฌํฑ ๋๋น๋ก ๋ ๋๋งํ ๋ค์ ์ถ์ํฉ๋๋ค. ์ฌ์ฉ์ ํฌ๊ธฐ ์กฐ์ ์ ํ์ฉํ์ธ์. ๋นํ์ฑํํ๋ ๊ฒ์ ์ฃผ์ ์ ๊ทผ์ฑ ์ฅ๋ฒฝ์ด๋ฉฐ ๋ธ๋ผ์ฐ์ ํ๋/์ถ์๋ฅผ ๋ฐฉํดํฉ๋๋ค.
<!-- โ ์ฌ์ฉ์ ํ๋ ๋นํ์ฑํ (์ ๊ทผ์ฑ ์๋ฐ) --> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <!-- โ ์ฌ๋ฐ๋ฆ: ์ต๋ 5๋ฐฐ ํ๋ ํ์ฉ --> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5">
Apple์ Human Interface Guidelines์ WCAG 2.5.5๋ ์ต์ 44ร44 CSS ํฝ์ ํญ ๋์์ ๊ถ์ฅํฉ๋๋ค. ์์ ๋์์ ์ฌ์ฉ์๋ฅผ ๋ถํธํ๊ฒ ํ๊ณ ์๋ชป๋ ํญ์ ์ ๋ฐํฉ๋๋ค. ํจ๋ฉ์ด๋ ๊ฐ์ ์์๋ฅผ ์ฌ์ฉํ์ฌ ์๊ฐ์ ํฌ๊ธฐ๋ฅผ ๋ณ๊ฒฝํ์ง ์๊ณ ํด๋ฆญ ๊ฐ๋ฅํ ์์ญ์ ํ์ฅํ ์ ์์ต๋๋ค.
/* ์๊ฐ์ ๋ณ๊ฒฝ ์์ด ํญ ์์ญ ํ์ฅ */ .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; }
margin-left ๋์ margin-inline-start์ ๊ฐ์ CSS ๋
ผ๋ฆฌ์ ์์ฑ์ ๋ฌธ์์ ์ฐ๊ธฐ ๋ชจ๋์ ๋ฐ๋ผ ๋ฐฉํฅ์ ์๋์ผ๋ก ๋ค์ง์ด RTL ์ง์์ ๊ฑฐ์ ์์ํ๊ฒ ๋ง๋ญ๋๋ค.
/* ๋ฌผ๋ฆฌ์ (RTL์์ ๋ค์งํ์ง ์์) */ .icon { margin-right: 8px; } /* ๋ ผ๋ฆฌ์ (RTL์์ ์๋์ผ๋ก ๋ค์งํ) */ .icon { margin-inline-end: 8px; } /* ๋ ๋ง์ ๋ ผ๋ฆฌ์ ์์ฑ ์์ */ .card { padding-inline: 16px; /* ์ผ์ชฝ + ์ค๋ฅธ์ชฝ */ padding-block: 12px; /* ์์ชฝ + ์๋์ชฝ */ border-start-start-radius: 12px; /* LTR์์ ์ผ์ชฝ ์ */ }
๋ชจ๋ฐ์ผ ๋ธ๋ผ์ฐ์ ์์ 100vh๋ ๋ธ๋ผ์ฐ์ ํฌ๋กฌ(์ฃผ์์ฐฝ)์ ํฌํจํ์ฌ ๋ง๋๊ฐ ๋ํ๋ ๋ ์ค๋ฒํ๋ก๋ฅผ ์ ๋ฐํฉ๋๋ค. ์๋ก์ด dvh(๋์ ๋ทฐํฌํธ ๋์ด) ๋จ์๋ ๋ธ๋ผ์ฐ์ ํฌ๋กฌ์ด ํ์๋๊ฑฐ๋ ์จ๊ฒจ์ง ๋ ์กฐ์ ๋ฉ๋๋ค.
/* โ ๋ชจ๋ฐ์ผ์์ ๋ธ๋ผ์ฐ์ ํฌ๋กฌ ๋ํ๋ ๋ ์ค๋ฒํ๋ก */ .hero { height: 100vh; } /* โ ๊ตฌํ ๋ธ๋ผ์ฐ์ ์ฉ ํด๋ฐฑ ํฌํจ */ .hero { height: 100vh; /* ํด๋ฐฑ */ height: 100dvh; /* ๋์ ๋ทฐํฌํธ ๋์ด */ }
svh(์์ ๋ทฐํฌํธ, ํญ์ ํฌ๋กฌ ์ ์ธ) ๋ฐ lvh(ํฐ ๋ทฐํฌํธ, ํญ์ ํฌ๋กฌ ๊ณต๊ฐ ํฌํจ)๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.๋ฐ์ํํ ์คํธ๋๊ตฌ ๋ ๋ณด๊ธฐ
์ด ํ๋ค์ ์ค์ ๋ก ์ ์ฉํด ๋ณด์ธ์
๋ฐ์ํ ๋ณ๊ฒฝ ์ฌํญ์ด ๋ชจ๋ฐ์ผ, ํ๋ธ๋ฆฟ, ๋ฐ์คํฌํฑ์์ ์ด๋ป๊ฒ ๋ณด์ด๋์ง ์ฆ์, ๋ฌด๋ฃ๋ก, ํ์๊ฐ์ ์์ด ํ ์คํธํ์ธ์.
์ง๊ธ ์น์ฌ์ดํธ ํ ์คํธํ๊ธฐ