responsive-design
npx skills add https://github.com/supercent-io/skills-template --skill responsive-design
Agent 安装分布
Skill 文档
Responsive Design
When to use this skill
- ì ì¹ì¬ì´í¸/ì±: 모ë°ì¼-ë°ì¤í¬í± ê²¸ì© ë ì´ìì ì¤ê³
- ë ê±°ì ê°ì : ê³ ì ë ì´ììì ë°ìíì¼ë¡ ì í
- ì±ë¥ ìµì í: ëë°ì´ì¤ë³ ì´ë¯¸ì§ ìµì í
- ë¤ìí íë©´: íë¸ë¦¿, ë°ì¤í¬í±, ëí íë©´ ì§ì
Instructions
Step 1: Mobile-First ì ê·¼
ìì íë©´ë¶í° ì¤ê³íê³ ì ì§ì ì¼ë¡ íì¥í©ëë¤.
ìì:
/* 기본: 모ë°ì¼ (320px~) */
.container {
padding: 1rem;
font-size: 14px;
}
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
/* íë¸ë¦¿ (768px~) */
@media (min-width: 768px) {
.container {
padding: 2rem;
font-size: 16px;
}
.grid {
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
}
}
/* ë°ì¤í¬í± (1024px~) */
@media (min-width: 1024px) {
.container {
max-width: 1200px;
margin: 0 auto;
padding: 3rem;
}
.grid {
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
}
}
/* ëí íë©´ (1440px~) */
@media (min-width: 1440px) {
.grid {
grid-template-columns: repeat(4, 1fr);
}
}
Step 2: Flexbox/Grid ë ì´ìì
íëì ì¸ CSS ë ì´ìì ìì¤í ì íì©í©ëë¤.
Flexbox (1ì°¨ì ë ì´ìì):
/* ë¤ë¹ê²ì´ì
ë° */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
/* ì¹´ë 리ì¤í¸ */
.card-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
@media (min-width: 768px) {
.card-list {
flex-direction: row;
flex-wrap: wrap;
}
.card {
flex: 1 1 calc(50% - 0.5rem); /* 2 columns */
}
}
@media (min-width: 1024px) {
.card {
flex: 1 1 calc(33.333% - 0.667rem); /* 3 columns */
}
}
CSS Grid (2ì°¨ì ë ì´ìì):
/* ëìë³´ë ë ì´ìì */
.dashboard {
display: grid;
grid-template-areas:
"header"
"sidebar"
"main"
"footer";
gap: 1rem;
}
@media (min-width: 768px) {
.dashboard {
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: 250px 1fr;
}
}
@media (min-width: 1024px) {
.dashboard {
grid-template-columns: 300px 1fr;
}
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
Step 3: ë°ìí ì´ë¯¸ì§
ëë°ì´ì¤ì ë§ë ì´ë¯¸ì§ë¥¼ ì ê³µí©ëë¤.
srcset ì¬ì©:
<img
src="image-800.jpg"
srcset="
image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w,
image-1600.jpg 1600w
"
sizes="
(max-width: 600px) 100vw,
(max-width: 900px) 50vw,
33vw
"
alt="Responsive image"
/>
picture ìì (Art Direction):
<picture>
<!-- 모ë°ì¼: ì¸ë¡ ì´ë¯¸ì§ -->
<source media="(max-width: 767px)" srcset="portrait.jpg">
<!-- íë¸ë¦¿: ì ì¬ê°í ì´ë¯¸ì§ -->
<source media="(max-width: 1023px)" srcset="square.jpg">
<!-- ë°ì¤í¬í±: ê°ë¡ ì´ë¯¸ì§ -->
<img src="landscape.jpg" alt="Art direction example">
</picture>
CSS ë°°ê²½ ì´ë¯¸ì§:
.hero {
background-image: url('hero-mobile.jpg');
}
@media (min-width: 768px) {
.hero {
background-image: url('hero-tablet.jpg');
}
}
@media (min-width: 1024px) {
.hero {
background-image: url('hero-desktop.jpg');
}
}
/* ëë image-set() ì¬ì© */
.hero {
background-image: image-set(
url('hero-1x.jpg') 1x,
url('hero-2x.jpg') 2x
);
}
Step 4: ë°ìí íì´í¬ê·¸ëí¼
íë©´ í¬ê¸°ì ë°ë¼ í ì¤í¸ í¬ê¸°ë¥¼ ì¡°ì í©ëë¤.
clamp() í¨ì (ì ëì í¬ê¸°):
:root {
/* min, preferred, max */
--font-size-body: clamp(14px, 2.5vw, 18px);
--font-size-h1: clamp(24px, 5vw, 48px);
--font-size-h2: clamp(20px, 4vw, 36px);
}
body {
font-size: var(--font-size-body);
}
h1 {
font-size: var(--font-size-h1);
line-height: 1.2;
}
h2 {
font-size: var(--font-size-h2);
line-height: 1.3;
}
미ëì´ ì¿¼ë¦¬ ë°©ì:
body {
font-size: 14px;
line-height: 1.6;
}
@media (min-width: 768px) {
body { font-size: 16px; }
}
@media (min-width: 1024px) {
body { font-size: 18px; }
}
Step 5: Container Queries (ì 기ë¥)
ë¶ëª¨ 컨í ì´ë í¬ê¸°ì ë°ë¼ ì¤íì¼ ì ì©í©ëë¤.
.card-container {
container-type: inline-size;
container-name: card;
}
.card {
padding: 1rem;
}
.card h2 {
font-size: 1.2rem;
}
/* 컨í
ì´ëê° 400px ì´ìì¼ ë */
@container card (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
padding: 1.5rem;
}
.card h2 {
font-size: 1.5rem;
}
}
/* 컨í
ì´ëê° 600px ì´ìì¼ ë */
@container card (min-width: 600px) {
.card {
grid-template-columns: 300px 1fr;
padding: 2rem;
}
}
Output format
íì¤ ë¸ë ì´í¬í¬ì¸í¸
/* Mobile (default): 320px ~ 767px */
/* Tablet: 768px ~ 1023px */
/* Desktop: 1024px ~ 1439px */
/* Large: 1440px+ */
:root {
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
--breakpoint-2xl: 1536px;
}
/* ì¬ì© ìì */
@media (min-width: 768px) { /* Tablet */ }
@media (min-width: 1024px) { /* Desktop */ }
Constraints
íì ê·ì¹ (MUST)
-
Viewport ë©ííê·¸: HTMLì ë°ëì í¬í¨
<meta name="viewport" content="width=device-width, initial-scale=1.0"> -
Mobile-First: 모ë°ì¼ 기본, min-width 미ëì´ ì¿¼ë¦¬ ì¬ì©
- â
@media (min-width: 768px) - â
@media (max-width: 767px)(Desktop-first)
- â
-
ìë ë¨ì: px ëì rem, em, %, vw/vh ì¬ì©
- font-size: rem
- padding/margin: rem ëë em
- width: % ëë vw
ê¸ì§ ì¬í (MUST NOT)
-
ê³ ì ëë¹ ê¸ì§:
width: 1200pxì§ìmax-width: 1200pxì¬ì©
-
ì¤ë³µ ì½ë: 모ë ë¸ë ì´í¬í¬ì¸í¸ì ê°ì ì¤íì¼ ë°ë³µ ê¸ì§
- ê³µíµ ì¤íì¼ì 기본ì¼ë¡, ì°¨ì´ë§ 미ëì´ ì¿¼ë¦¬ì
Examples
ìì 1: ë°ìí ë¤ë¹ê²ì´ì
function ResponsiveNav() {
const [isOpen, setIsOpen] = useState(false);
return (
<nav className="navbar">
{/* ë¡ê³ */}
<a href="/" className="logo">MyApp</a>
{/* íë²ê±° ë²í¼ (모ë°ì¼) */}
<button
className="menu-toggle"
onClick={() => setIsOpen(!isOpen)}
aria-label="Toggle menu"
aria-expanded={isOpen}
>
<span></span>
<span></span>
<span></span>
</button>
{/* ë¤ë¹ê²ì´ì
ë§í¬ */}
<ul className={`nav-links ${isOpen ? 'active' : ''}`}>
<li><a href="/about">About</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
);
}
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
}
/* íë²ê±° ë²í¼ (모ë°ì¼ë§) */
.menu-toggle {
display: flex;
flex-direction: column;
gap: 4px;
}
.nav-links {
display: none;
position: absolute;
top: 60px;
left: 0;
right: 0;
background: white;
flex-direction: column;
}
.nav-links.active {
display: flex;
}
/* íë¸ë¦¿ ì´ì: íë²ê±° ì¨ê¸°ê³ íì íì */
@media (min-width: 768px) {
.menu-toggle {
display: none;
}
.nav-links {
display: flex;
position: static;
flex-direction: row;
gap: 2rem;
}
}
ìì 2: ë°ìí 그리ë ì¹´ë
function ProductGrid({ products }) {
return (
<div className="product-grid">
{products.map(product => (
<div key={product.id} className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p className="price">${product.price}</p>
<button>Add to Cart</button>
</div>
))}
</div>
);
}
.product-grid {
display: grid;
grid-template-columns: 1fr; /* 모ë°ì¼: 1 column */
gap: 1rem;
padding: 1rem;
}
@media (min-width: 640px) {
.product-grid {
grid-template-columns: repeat(2, 1fr); /* 2 columns */
}
}
@media (min-width: 1024px) {
.product-grid {
grid-template-columns: repeat(3, 1fr); /* 3 columns */
gap: 1.5rem;
}
}
@media (min-width: 1440px) {
.product-grid {
grid-template-columns: repeat(4, 1fr); /* 4 columns */
gap: 2rem;
}
}
.product-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
}
.product-card img {
width: 100%;
height: auto;
aspect-ratio: 1 / 1;
object-fit: cover;
}
Best practices
- 컨í ì´ë 쿼리 ì°ì : ê°ë¥íë©´ 미ëì´ ì¿¼ë¦¬ ëì 컨í ì´ë 쿼리
- Flexbox vs Grid: 1ì°¨ìì Flexbox, 2ì°¨ìì Grid
- ì±ë¥: ì´ë¯¸ì§ lazy loading, WebP í¬ë§· ì¬ì©
- í ì¤í¸: Chrome DevTools Device Mode, BrowserStack
References
Metadata
ë²ì
- íì¬ ë²ì : 1.0.0
- ìµì¢ ì ë°ì´í¸: 2025-01-01
- í¸í íë«í¼: Claude, ChatGPT, Gemini
ê´ë ¨ ì¤í¬
- ui-component-patterns: ë°ìí ì»´í¬ëí¸
- web-accessibility: ì ê·¼ì±ê³¼ í¨ê» ê³ ë ¤
íê·¸
#responsive #mobile-first #CSS #Flexbox #Grid #media-query #frontend