[{"data":1,"prerenderedAt":2272},["ShallowReactive",2],{"blog-post-\u002Fko\u002Fhtml-features-without-javascript":3,"blog-related-source":1980},{"id":4,"title":5,"body":6,"description":1969,"extension":1970,"meta":1971,"navigation":344,"path":1975,"seo":1976,"sitemap":1977,"stem":1978,"__hash__":1979},"ko\u002Fko\u002Fhtml-features-without-javascript.md","자바스크립트 없이 HTML만으로 되는 것들: 아직도 div로 다 만들고 있다면",{"type":7,"value":8,"toc":1950},"minimark",[9,14,18,56,64,79,85,92,95,106,169,192,287,297,299,307,313,416,419,489,495,497,504,515,663,701,754,775,777,784,787,943,950,952,959,965,1066,1129,1147,1149,1153,1156,1275,1314,1348,1359,1361,1365,1461,1520,1538,1540,1544,1554,1665,1685,1687,1694,1697,1796,1812,1814,1818,1915,1918,1939,1946],[10,11,13],"h2",{"id":12},"아코디언-하나-만드는-데-몇-줄이-필요할까","아코디언 하나 만드는 데 몇 줄이 필요할까?",[15,16,17],"p",{},"\"클릭하면 펼쳐지는 FAQ\"를 만든다고 해보자. 흔히 이렇게 시작한다.",[19,20,21,29,32,38,49],"ul",{},[22,23,24,28],"li",{},[25,26,27],"code",{},"div","로 제목과 내용을 만들고",[22,30,31],{},"클릭 이벤트(Click Event)를 달고",[22,33,34,37],{},[25,35,36],{},"isOpen"," 상태를 관리하고",[22,39,40,41,44,45,48],{},"키보드로도 열리게 ",[25,42,43],{},"tabindex","와 ",[25,46,47],{},"keydown","을 붙이고",[22,50,51,52,55],{},"스크린 리더(Screen Reader)를 위해 ",[25,53,54],{},"aria-expanded","를 맞춘다",[15,57,58,59,63],{},"그런데 HTML에는 이걸 ",[60,61,62],"strong",{},"태그 두 개로"," 끝내는 방법이 이미 있다. 바로 아래가 실제로 동작하는 예시다. 눌러보자.",[65,66,67,71],"details",{},[68,69,70],"summary",{},"HTML만으로 만든 아코디언인가요?",[15,72,73,74,44,76,78],{},"네. 자바스크립트 한 줄 없이 ",[25,75,65],{},[25,77,68],{}," 태그만 썼어요. 키보드(Tab → Enter)로도 열리고, 스크린 리더도 \"펼쳐짐\u002F접힘\" 상태를 읽어줍니다.",[80,81,82],"blockquote",{},[15,83,84],{},"브라우저는 생각보다 많은 것을 기본으로 제공한다. 직접 만들기 전에 \"HTML에 이미 있나?\"부터 확인하면 코드도 줄고, 접근성(Accessibility)과 성능은 공짜로 따라온다.",[15,86,87,88,91],{},"이 글에서는 그렇게 ",[60,89,90],{},"HTML만으로 해결되는 기능","들을 모아봤다.",[93,94],"hr",{},[10,96,98,99,102,103],{"id":97},"_1-아코디언-details-summary","1. 아코디언: ",[25,100,101],{},"\u003Cdetails>"," + ",[25,104,105],{},"\u003Csummary>",[107,108,113],"pre",{"className":109,"code":110,"language":111,"meta":112,"style":112},"language-html shiki shiki-themes github-light github-dark","\u003Cdetails>\n  \u003Csummary>배송은 얼마나 걸리나요?\u003C\u002Fsummary>\n  \u003Cp>평일 기준 1~3일 소요됩니다.\u003C\u002Fp>\n\u003C\u002Fdetails>\n","html","",[25,114,115,130,145,159],{"__ignoreMap":112},[116,117,120,124,127],"span",{"class":118,"line":119},"line",1,[116,121,123],{"class":122},"sVt8B","\u003C",[116,125,65],{"class":126},"s9eBZ",[116,128,129],{"class":122},">\n",[116,131,133,136,138,141,143],{"class":118,"line":132},2,[116,134,135],{"class":122},"  \u003C",[116,137,68],{"class":126},[116,139,140],{"class":122},">배송은 얼마나 걸리나요?\u003C\u002F",[116,142,68],{"class":126},[116,144,129],{"class":122},[116,146,148,150,152,155,157],{"class":118,"line":147},3,[116,149,135],{"class":122},[116,151,15],{"class":126},[116,153,154],{"class":122},">평일 기준 1~3일 소요됩니다.\u003C\u002F",[116,156,15],{"class":126},[116,158,129],{"class":122},[116,160,162,165,167],{"class":118,"line":161},4,[116,163,164],{"class":122},"\u003C\u002F",[116,166,65],{"class":126},[116,168,129],{"class":122},[19,170,171,178],{},[22,172,173,174,177],{},"처음부터 펼쳐두고 싶으면 ",[25,175,176],{},"open"," 속성을 붙인다.",[22,179,180,181,184,185,187,188,191],{},"같은 ",[25,182,183],{},"name"," 속성을 준 ",[25,186,65],{},"끼리는 ",[60,189,190],{},"하나만 열리는"," 배타적 아코디언(Exclusive Accordion)이 된다. 하나를 열면 나머지가 자동으로 닫힌다.",[107,193,195],{"className":109,"code":194,"language":111,"meta":112,"style":112},"\u003Cdetails name=\"faq\">\u003Csummary>질문 1\u003C\u002Fsummary>답변 1\u003C\u002Fdetails>\n\u003Cdetails name=\"faq\">\u003Csummary>질문 2\u003C\u002Fsummary>답변 2\u003C\u002Fdetails>\n\u003Cdetails name=\"faq\">\u003Csummary>질문 3\u003C\u002Fsummary>답변 3\u003C\u002Fdetails>\n",[25,196,197,231,259],{"__ignoreMap":112},[116,198,199,201,203,207,210,214,217,219,222,224,227,229],{"class":118,"line":119},[116,200,123],{"class":122},[116,202,65],{"class":126},[116,204,206],{"class":205},"sScJk"," name",[116,208,209],{"class":122},"=",[116,211,213],{"class":212},"sZZnC","\"faq\"",[116,215,216],{"class":122},">\u003C",[116,218,68],{"class":126},[116,220,221],{"class":122},">질문 1\u003C\u002F",[116,223,68],{"class":126},[116,225,226],{"class":122},">답변 1\u003C\u002F",[116,228,65],{"class":126},[116,230,129],{"class":122},[116,232,233,235,237,239,241,243,245,247,250,252,255,257],{"class":118,"line":132},[116,234,123],{"class":122},[116,236,65],{"class":126},[116,238,206],{"class":205},[116,240,209],{"class":122},[116,242,213],{"class":212},[116,244,216],{"class":122},[116,246,68],{"class":126},[116,248,249],{"class":122},">질문 2\u003C\u002F",[116,251,68],{"class":126},[116,253,254],{"class":122},">답변 2\u003C\u002F",[116,256,65],{"class":126},[116,258,129],{"class":122},[116,260,261,263,265,267,269,271,273,275,278,280,283,285],{"class":118,"line":147},[116,262,123],{"class":122},[116,264,65],{"class":126},[116,266,206],{"class":205},[116,268,209],{"class":122},[116,270,213],{"class":212},[116,272,216],{"class":122},[116,274,68],{"class":126},[116,276,277],{"class":122},">질문 3\u003C\u002F",[116,279,68],{"class":126},[116,281,282],{"class":122},">답변 3\u003C\u002F",[116,284,65],{"class":126},[116,286,129],{"class":122},[288,289,290],"tip",{},[80,291,292],{},[15,293,294,296],{},[25,295,183],{}," 속성은 2024년부터 주요 브라우저(Chrome, Safari, Firefox)에서 모두 지원된다. 지원하지 않는 브라우저에서도 그냥 \"여러 개 열리는 아코디언\"으로 동작하니 깨질 걱정은 없다.",[93,298],{},[10,300,302,303,306],{"id":301},"_2-팝오버-popover-속성","2. 팝오버: ",[25,304,305],{},"popover"," 속성",[15,308,309,310,312],{},"드롭다운 메뉴, 툴팁, 알림 패널처럼 \"버튼 누르면 위에 뜨는 것\"은 ",[25,311,305],{}," 속성 하나로 된다.",[107,314,316],{"className":109,"code":315,"language":111,"meta":112,"style":112},"\u003Cbutton popovertarget=\"menu\">메뉴 열기\u003C\u002Fbutton>\n\n\u003Cdiv id=\"menu\" popover>\n  \u003Ca href=\"\u002Fprofile\">내 정보\u003C\u002Fa>\n  \u003Ca href=\"\u002Flogout\">로그아웃\u003C\u002Fa>\n\u003C\u002Fdiv>\n",[25,317,318,340,346,364,386,407],{"__ignoreMap":112},[116,319,320,322,325,328,330,333,336,338],{"class":118,"line":119},[116,321,123],{"class":122},[116,323,324],{"class":126},"button",[116,326,327],{"class":205}," popovertarget",[116,329,209],{"class":122},[116,331,332],{"class":212},"\"menu\"",[116,334,335],{"class":122},">메뉴 열기\u003C\u002F",[116,337,324],{"class":126},[116,339,129],{"class":122},[116,341,342],{"class":118,"line":132},[116,343,345],{"emptyLinePlaceholder":344},true,"\n",[116,347,348,350,352,355,357,359,362],{"class":118,"line":147},[116,349,123],{"class":122},[116,351,27],{"class":126},[116,353,354],{"class":205}," id",[116,356,209],{"class":122},[116,358,332],{"class":212},[116,360,361],{"class":205}," popover",[116,363,129],{"class":122},[116,365,366,368,371,374,376,379,382,384],{"class":118,"line":161},[116,367,135],{"class":122},[116,369,370],{"class":126},"a",[116,372,373],{"class":205}," href",[116,375,209],{"class":122},[116,377,378],{"class":212},"\"\u002Fprofile\"",[116,380,381],{"class":122},">내 정보\u003C\u002F",[116,383,370],{"class":126},[116,385,129],{"class":122},[116,387,389,391,393,395,397,400,403,405],{"class":118,"line":388},5,[116,390,135],{"class":122},[116,392,370],{"class":126},[116,394,373],{"class":205},[116,396,209],{"class":122},[116,398,399],{"class":212},"\"\u002Flogout\"",[116,401,402],{"class":122},">로그아웃\u003C\u002F",[116,404,370],{"class":126},[116,406,129],{"class":122},[116,408,410,412,414],{"class":118,"line":409},6,[116,411,164],{"class":122},[116,413,27],{"class":126},[116,415,129],{"class":122},[15,417,418],{},"이것만으로 브라우저가 알아서 처리해주는 것들이 꽤 많다.",[420,421,422,437],"table",{},[423,424,425],"thead",{},[426,427,428,432],"tr",{},[429,430,431],"th",{},"직접 만들 때 해야 했던 일",[429,433,434,436],{},[25,435,305],{},"가 기본으로 해주는 것",[438,439,440,452,460,471,482],"tbody",{},[426,441,442,446],{},[443,444,445],"td",{},"열기\u002F닫기 상태 관리",[443,447,448,451],{},[25,449,450],{},"popovertarget"," 버튼이 토글",[426,453,454,457],{},[443,455,456],{},"바깥 클릭하면 닫기",[443,458,459],{},"기본 동작 (Light Dismiss)",[426,461,462,468],{},[443,463,464,467],{},[25,465,466],{},"Esc"," 키로 닫기",[443,469,470],{},"기본 동작",[426,472,473,479],{},[443,474,475,478],{},[25,476,477],{},"z-index"," 싸움",[443,480,481],{},"최상위 레이어(Top Layer)에 그려져서 무조건 맨 위",[426,483,484,487],{},[443,485,486],{},"다른 팝오버 열면 기존 것 닫기",[443,488,470],{},[15,490,491,494],{},[25,492,493],{},"z-index: 99999","를 붙여본 적이 있다면 마지막에서 두 번째 줄이 특히 반가울 것이다.",[93,496],{},[10,498,500,501],{"id":499},"_3-모달-dialog","3. 모달: ",[25,502,503],{},"\u003Cdialog>",[15,505,506,507,510,511,514],{},"확인창, 로그인 모달처럼 ",[60,508,509],{},"뒤 화면을 막는 창","은 ",[25,512,513],{},"dialog","를 쓴다. 여기는 여는 데만 자바스크립트 한 줄이 필요하다.",[107,516,518],{"className":109,"code":517,"language":111,"meta":112,"style":112},"\u003Cbutton onclick=\"confirmDialog.showModal()\">삭제\u003C\u002Fbutton>\n\n\u003Cdialog id=\"confirmDialog\">\n  \u003Cp>정말 삭제할까요?\u003C\u002Fp>\n  \u003Cform method=\"dialog\">\n    \u003Cbutton value=\"cancel\">취소\u003C\u002Fbutton>\n    \u003Cbutton value=\"ok\">삭제\u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Fdialog>\n",[25,519,520,553,557,572,585,602,624,644,654],{"__ignoreMap":112},[116,521,522,524,526,529,531,534,537,540,543,546,549,551],{"class":118,"line":119},[116,523,123],{"class":122},[116,525,324],{"class":126},[116,527,528],{"class":205}," onclick",[116,530,209],{"class":122},[116,532,533],{"class":212},"\"",[116,535,536],{"class":122},"confirmDialog",[116,538,539],{"class":212},".",[116,541,542],{"class":205},"showModal",[116,544,545],{"class":212},"()\"",[116,547,548],{"class":122},">삭제\u003C\u002F",[116,550,324],{"class":126},[116,552,129],{"class":122},[116,554,555],{"class":118,"line":132},[116,556,345],{"emptyLinePlaceholder":344},[116,558,559,561,563,565,567,570],{"class":118,"line":147},[116,560,123],{"class":122},[116,562,513],{"class":126},[116,564,354],{"class":205},[116,566,209],{"class":122},[116,568,569],{"class":212},"\"confirmDialog\"",[116,571,129],{"class":122},[116,573,574,576,578,581,583],{"class":118,"line":161},[116,575,135],{"class":122},[116,577,15],{"class":126},[116,579,580],{"class":122},">정말 삭제할까요?\u003C\u002F",[116,582,15],{"class":126},[116,584,129],{"class":122},[116,586,587,589,592,595,597,600],{"class":118,"line":388},[116,588,135],{"class":122},[116,590,591],{"class":126},"form",[116,593,594],{"class":205}," method",[116,596,209],{"class":122},[116,598,599],{"class":212},"\"dialog\"",[116,601,129],{"class":122},[116,603,604,607,609,612,614,617,620,622],{"class":118,"line":409},[116,605,606],{"class":122},"    \u003C",[116,608,324],{"class":126},[116,610,611],{"class":205}," value",[116,613,209],{"class":122},[116,615,616],{"class":212},"\"cancel\"",[116,618,619],{"class":122},">취소\u003C\u002F",[116,621,324],{"class":126},[116,623,129],{"class":122},[116,625,627,629,631,633,635,638,640,642],{"class":118,"line":626},7,[116,628,606],{"class":122},[116,630,324],{"class":126},[116,632,611],{"class":205},[116,634,209],{"class":122},[116,636,637],{"class":212},"\"ok\"",[116,639,548],{"class":122},[116,641,324],{"class":126},[116,643,129],{"class":122},[116,645,647,650,652],{"class":118,"line":646},8,[116,648,649],{"class":122},"  \u003C\u002F",[116,651,591],{"class":126},[116,653,129],{"class":122},[116,655,657,659,661],{"class":118,"line":656},9,[116,658,164],{"class":122},[116,660,513],{"class":126},[116,662,129],{"class":122},[19,664,665,671,689,694],{},[22,666,667,670],{},[25,668,669],{},"showModal()","로 열면 뒤 배경을 클릭할 수 없게 막히고, 포커스가 모달 안에 갇힌다 (Focus Trap).",[22,672,673,676,677,680,681,684,685,688],{},[25,674,675],{},"form method=\"dialog\""," 안의 버튼을 누르면 ",[60,678,679],{},"자동으로 닫히고",", 누른 버튼의 ",[25,682,683],{},"value","가 ",[25,686,687],{},"dialog.returnValue","에 담긴다.",[22,690,691,693],{},[25,692,466],{},"로 닫기도 기본으로 된다.",[22,695,696,697,700],{},"뒤 배경은 CSS의 ",[25,698,699],{},"::backdrop","으로 꾸민다.",[107,702,706],{"className":703,"code":704,"language":705,"meta":112,"style":112},"language-css shiki shiki-themes github-light github-dark","dialog::backdrop {\n  background: rgb(0 0 0 \u002F 0.5);\n}\n","css",[25,707,708,717,749],{"__ignoreMap":112},[116,709,710,712,714],{"class":118,"line":119},[116,711,513],{"class":126},[116,713,699],{"class":205},[116,715,716],{"class":122}," {\n",[116,718,719,723,726,729,732,735,738,740,743,746],{"class":118,"line":132},[116,720,722],{"class":721},"sj4cs","  background",[116,724,725],{"class":122},": ",[116,727,728],{"class":721},"rgb",[116,730,731],{"class":122},"(",[116,733,734],{"class":721},"0",[116,736,737],{"class":721}," 0",[116,739,737],{"class":721},[116,741,742],{"class":122}," \u002F ",[116,744,745],{"class":721},"0.5",[116,747,748],{"class":122},");\n",[116,750,751],{"class":118,"line":147},[116,752,753],{"class":122},"}\n",[755,756,757],"note",{},[80,758,759],{},[15,760,761,44,763,765,766,774],{},[25,762,305],{},[25,764,513],{}," 중 무엇을 쓸지 헷갈린다면: ",[60,767,768,769,771,772],{},"뒤 화면을 막아야 하면 ",[25,770,513],{},"(모달), 막지 않고 위에 살짝 띄우기만 하면 ",[25,773,305],{}," 라고 기억하면 된다.",[93,776],{},[10,778,780,781],{"id":779},"_4-자동완성-datalist","4. 자동완성: ",[25,782,783],{},"\u003Cdatalist>",[15,785,786],{},"검색창에 추천 목록을 띄우는 데도 라이브러리가 필요 없다.",[107,788,790],{"className":109,"code":789,"language":111,"meta":112,"style":112},"\u003Clabel for=\"framework\">프레임워크\u003C\u002Flabel>\n\u003Cinput id=\"framework\" list=\"frameworks\" \u002F>\n\n\u003Cdatalist id=\"frameworks\">\n  \u003Coption value=\"Nuxt\">\u003C\u002Foption>\n  \u003Coption value=\"Next.js\">\u003C\u002Foption>\n  \u003Coption value=\"SvelteKit\">\u003C\u002Foption>\n  \u003Coption value=\"Astro\">\u003C\u002Foption>\n\u003C\u002Fdatalist>\n",[25,791,792,814,838,842,857,878,897,916,935],{"__ignoreMap":112},[116,793,794,796,799,802,804,807,810,812],{"class":118,"line":119},[116,795,123],{"class":122},[116,797,798],{"class":126},"label",[116,800,801],{"class":205}," for",[116,803,209],{"class":122},[116,805,806],{"class":212},"\"framework\"",[116,808,809],{"class":122},">프레임워크\u003C\u002F",[116,811,798],{"class":126},[116,813,129],{"class":122},[116,815,816,818,821,823,825,827,830,832,835],{"class":118,"line":132},[116,817,123],{"class":122},[116,819,820],{"class":126},"input",[116,822,354],{"class":205},[116,824,209],{"class":122},[116,826,806],{"class":212},[116,828,829],{"class":205}," list",[116,831,209],{"class":122},[116,833,834],{"class":212},"\"frameworks\"",[116,836,837],{"class":122}," \u002F>\n",[116,839,840],{"class":118,"line":147},[116,841,345],{"emptyLinePlaceholder":344},[116,843,844,846,849,851,853,855],{"class":118,"line":161},[116,845,123],{"class":122},[116,847,848],{"class":126},"datalist",[116,850,354],{"class":205},[116,852,209],{"class":122},[116,854,834],{"class":212},[116,856,129],{"class":122},[116,858,859,861,864,866,868,871,874,876],{"class":118,"line":388},[116,860,135],{"class":122},[116,862,863],{"class":126},"option",[116,865,611],{"class":205},[116,867,209],{"class":122},[116,869,870],{"class":212},"\"Nuxt\"",[116,872,873],{"class":122},">\u003C\u002F",[116,875,863],{"class":126},[116,877,129],{"class":122},[116,879,880,882,884,886,888,891,893,895],{"class":118,"line":409},[116,881,135],{"class":122},[116,883,863],{"class":126},[116,885,611],{"class":205},[116,887,209],{"class":122},[116,889,890],{"class":212},"\"Next.js\"",[116,892,873],{"class":122},[116,894,863],{"class":126},[116,896,129],{"class":122},[116,898,899,901,903,905,907,910,912,914],{"class":118,"line":626},[116,900,135],{"class":122},[116,902,863],{"class":126},[116,904,611],{"class":205},[116,906,209],{"class":122},[116,908,909],{"class":212},"\"SvelteKit\"",[116,911,873],{"class":122},[116,913,863],{"class":126},[116,915,129],{"class":122},[116,917,918,920,922,924,926,929,931,933],{"class":118,"line":646},[116,919,135],{"class":122},[116,921,863],{"class":126},[116,923,611],{"class":205},[116,925,209],{"class":122},[116,927,928],{"class":212},"\"Astro\"",[116,930,873],{"class":122},[116,932,863],{"class":126},[116,934,129],{"class":122},[116,936,937,939,941],{"class":118,"line":656},[116,938,164],{"class":122},[116,940,848],{"class":126},[116,942,129],{"class":122},[15,944,945,946,949],{},"입력하면 목록이 필터링되고, 목록에 없는 값도 자유롭게 입력할 수 있다. 디자인을 세밀하게 바꿀 수는 없어서 서비스의 메인 검색창보다는 ",[60,947,948],{},"관리자 페이지나 사내 도구","에 특히 잘 맞는다.",[93,951],{},[10,953,955,956,306],{"id":954},"_5-모바일-키보드까지-챙기는-input-속성","5. 모바일 키보드까지 챙기는 ",[25,957,958],{},"\u003Cinput>",[15,960,961,964],{},[25,962,963],{},"type=\"text\""," 하나로 모든 입력을 받고 있다면 모바일 사용자는 매번 키보드를 바꾸고 있는 셈이다.",[420,966,967,980],{},[423,968,969],{},[426,970,971,974,977],{},[429,972,973],{},"속성",[429,975,976],{},"효과",[429,978,979],{},"쓰는 곳",[438,981,982,998,1011,1024,1037,1050],{},[426,983,984,989,995],{},[443,985,986],{},[25,987,988],{},"type=\"email\"",[443,990,991,994],{},[25,992,993],{},"@","가 있는 키보드 + 형식 검사",[443,996,997],{},"이메일",[426,999,1000,1005,1008],{},[443,1001,1002],{},[25,1003,1004],{},"type=\"tel\"",[443,1006,1007],{},"전화번호 키패드",[443,1009,1010],{},"전화번호",[426,1012,1013,1018,1021],{},[443,1014,1015],{},[25,1016,1017],{},"inputmode=\"numeric\"",[443,1019,1020],{},"숫자 키패드 (값은 문자열로 유지)",[443,1022,1023],{},"인증번호, 카드번호",[426,1025,1026,1031,1034],{},[443,1027,1028],{},[25,1029,1030],{},"enterkeyhint=\"search\"",[443,1032,1033],{},"키보드의 엔터 키가 \"검색\"으로 바뀜",[443,1035,1036],{},"검색창",[426,1038,1039,1044,1047],{},[443,1040,1041],{},[25,1042,1043],{},"autocomplete=\"one-time-code\"",[443,1045,1046],{},"문자로 온 인증번호를 키보드 위에 추천",[443,1048,1049],{},"휴대폰 인증",[426,1051,1052,1060,1063],{},[443,1053,1054,742,1057],{},[25,1055,1056],{},"type=\"date\"",[25,1058,1059],{},"type=\"color\"",[443,1061,1062],{},"브라우저 기본 날짜\u002F색상 선택기",[443,1064,1065],{},"간단한 날짜, 색상 입력",[107,1067,1069],{"className":109,"code":1068,"language":111,"meta":112,"style":112},"\u003C!-- 문자로 받은 6자리 인증번호 입력칸 -->\n\u003Cinput\n  inputmode=\"numeric\"\n  autocomplete=\"one-time-code\"\n  maxlength=\"6\"\n  enterkeyhint=\"done\"\n\u002F>\n",[25,1070,1071,1077,1084,1094,1104,1114,1124],{"__ignoreMap":112},[116,1072,1073],{"class":118,"line":119},[116,1074,1076],{"class":1075},"sJ8bj","\u003C!-- 문자로 받은 6자리 인증번호 입력칸 -->\n",[116,1078,1079,1081],{"class":118,"line":132},[116,1080,123],{"class":122},[116,1082,1083],{"class":126},"input\n",[116,1085,1086,1089,1091],{"class":118,"line":147},[116,1087,1088],{"class":205},"  inputmode",[116,1090,209],{"class":122},[116,1092,1093],{"class":212},"\"numeric\"\n",[116,1095,1096,1099,1101],{"class":118,"line":161},[116,1097,1098],{"class":205},"  autocomplete",[116,1100,209],{"class":122},[116,1102,1103],{"class":212},"\"one-time-code\"\n",[116,1105,1106,1109,1111],{"class":118,"line":388},[116,1107,1108],{"class":205},"  maxlength",[116,1110,209],{"class":122},[116,1112,1113],{"class":212},"\"6\"\n",[116,1115,1116,1119,1121],{"class":118,"line":409},[116,1117,1118],{"class":205},"  enterkeyhint",[116,1120,209],{"class":122},[116,1122,1123],{"class":212},"\"done\"\n",[116,1125,1126],{"class":118,"line":626},[116,1127,1128],{"class":122},"\u002F>\n",[1130,1131,1132],"warning",{},[80,1133,1134],{},[15,1135,1136,1137,1140,1141,1143,1144,1146],{},"카드번호나 인증번호에 ",[25,1138,1139],{},"type=\"number\"","를 쓰면 안 된다. 앞자리 ",[25,1142,734],{},"이 사라지거나, 마우스 휠에 값이 바뀌는 문제가 생긴다. \"숫자 키패드만 필요한 문자열\"이면 ",[25,1145,1017],{},"이 정답이다.",[93,1148],{},[10,1150,1152],{"id":1151},"_6-폼-검증-속성-css-한-줄","6. 폼 검증: 속성 + CSS 한 줄",[15,1154,1155],{},"입력값 검사도 상당 부분 HTML 속성으로 된다.",[107,1157,1159],{"className":109,"code":1158,"language":111,"meta":112,"style":112},"\u003Cform>\n  \u003Cinput type=\"email\" required placeholder=\"이메일\" \u002F>\n  \u003Cinput\n    type=\"password\"\n    required\n    minlength=\"8\"\n    pattern=\"(?=.*\\d)(?=.*[a-zA-Z]).*\"\n    title=\"영문과 숫자를 모두 포함해주세요\"\n  \u002F>\n  \u003Cbutton>가입하기\u003C\u002Fbutton>\n\u003C\u002Fform>\n",[25,1160,1161,1169,1196,1202,1212,1217,1227,1237,1247,1252,1266],{"__ignoreMap":112},[116,1162,1163,1165,1167],{"class":118,"line":119},[116,1164,123],{"class":122},[116,1166,591],{"class":126},[116,1168,129],{"class":122},[116,1170,1171,1173,1175,1178,1180,1183,1186,1189,1191,1194],{"class":118,"line":132},[116,1172,135],{"class":122},[116,1174,820],{"class":126},[116,1176,1177],{"class":205}," type",[116,1179,209],{"class":122},[116,1181,1182],{"class":212},"\"email\"",[116,1184,1185],{"class":205}," required",[116,1187,1188],{"class":205}," placeholder",[116,1190,209],{"class":122},[116,1192,1193],{"class":212},"\"이메일\"",[116,1195,837],{"class":122},[116,1197,1198,1200],{"class":118,"line":147},[116,1199,135],{"class":122},[116,1201,1083],{"class":126},[116,1203,1204,1207,1209],{"class":118,"line":161},[116,1205,1206],{"class":205},"    type",[116,1208,209],{"class":122},[116,1210,1211],{"class":212},"\"password\"\n",[116,1213,1214],{"class":118,"line":388},[116,1215,1216],{"class":205},"    required\n",[116,1218,1219,1222,1224],{"class":118,"line":409},[116,1220,1221],{"class":205},"    minlength",[116,1223,209],{"class":122},[116,1225,1226],{"class":212},"\"8\"\n",[116,1228,1229,1232,1234],{"class":118,"line":626},[116,1230,1231],{"class":205},"    pattern",[116,1233,209],{"class":122},[116,1235,1236],{"class":212},"\"(?=.*\\d)(?=.*[a-zA-Z]).*\"\n",[116,1238,1239,1242,1244],{"class":118,"line":646},[116,1240,1241],{"class":205},"    title",[116,1243,209],{"class":122},[116,1245,1246],{"class":212},"\"영문과 숫자를 모두 포함해주세요\"\n",[116,1248,1249],{"class":118,"line":656},[116,1250,1251],{"class":122},"  \u002F>\n",[116,1253,1255,1257,1259,1262,1264],{"class":118,"line":1254},10,[116,1256,135],{"class":122},[116,1258,324],{"class":126},[116,1260,1261],{"class":122},">가입하기\u003C\u002F",[116,1263,324],{"class":126},[116,1265,129],{"class":122},[116,1267,1269,1271,1273],{"class":118,"line":1268},11,[116,1270,164],{"class":122},[116,1272,591],{"class":126},[116,1274,129],{"class":122},[19,1276,1277,1299],{},[22,1278,1279,1282,1283,1282,1286,1282,1289,1282,1292,1282,1295,1298],{},[25,1280,1281],{},"required",", ",[25,1284,1285],{},"minlength",[25,1287,1288],{},"maxlength",[25,1290,1291],{},"min",[25,1293,1294],{},"max",[25,1296,1297],{},"pattern","만으로 기본 검사가 되고, 제출 시 브라우저가 알아서 막고 안내해준다.",[22,1300,1301,1302,1305,1306,1309,1310,1313],{},"에러 스타일은 CSS의 ",[25,1303,1304],{},":user-invalid","를 쓰면 좋다. ",[25,1307,1308],{},":invalid","와 달리 ",[60,1311,1312],{},"사용자가 입력을 건드린 뒤에만"," 빨갛게 표시돼서, 페이지에 들어오자마자 온통 빨간 화면이 되는 문제가 없다.",[107,1315,1317],{"className":703,"code":1316,"language":705,"meta":112,"style":112},"input:user-invalid {\n  border-color: crimson;\n}\n",[25,1318,1319,1331,1344],{"__ignoreMap":112},[116,1320,1321,1323,1326,1329],{"class":118,"line":119},[116,1322,820],{"class":126},[116,1324,1325],{"class":122},":",[116,1327,1328],{"class":126},"user-invalid",[116,1330,716],{"class":122},[116,1332,1333,1336,1338,1341],{"class":118,"line":132},[116,1334,1335],{"class":721},"  border-color",[116,1337,725],{"class":122},[116,1339,1340],{"class":721},"crimson",[116,1342,1343],{"class":122},";\n",[116,1345,1346],{"class":118,"line":147},[116,1347,753],{"class":122},[755,1349,1350],{},[80,1351,1352],{},[15,1353,1354,1355,1358],{},"HTML 검증은 사용자 편의를 위한 것이지 보안이 아니다. 개발자 도구로 속성을 지우면 그만이니, ",[60,1356,1357],{},"서버에서도 반드시 다시 검사","해야 한다.",[93,1360],{},[10,1362,1364],{"id":1363},"_7-이미지-속성-몇-개로-성능-챙기기","7. 이미지: 속성 몇 개로 성능 챙기기",[107,1366,1368],{"className":109,"code":1367,"language":111,"meta":112,"style":112},"\u003Cimg\n  src=\"\u002Fimages\u002Fhero-800.jpg\"\n  srcset=\"\u002Fimages\u002Fhero-400.jpg 400w, \u002Fimages\u002Fhero-800.jpg 800w, \u002Fimages\u002Fhero-1600.jpg 1600w\"\n  sizes=\"(max-width: 600px) 100vw, 800px\"\n  width=\"800\"\n  height=\"450\"\n  loading=\"lazy\"\n  decoding=\"async\"\n  alt=\"노트북 앞에서 코드를 작성하는 개발자\"\n\u002F>\n",[25,1369,1370,1377,1387,1397,1407,1417,1427,1437,1447,1457],{"__ignoreMap":112},[116,1371,1372,1374],{"class":118,"line":119},[116,1373,123],{"class":122},[116,1375,1376],{"class":126},"img\n",[116,1378,1379,1382,1384],{"class":118,"line":132},[116,1380,1381],{"class":205},"  src",[116,1383,209],{"class":122},[116,1385,1386],{"class":212},"\"\u002Fimages\u002Fhero-800.jpg\"\n",[116,1388,1389,1392,1394],{"class":118,"line":147},[116,1390,1391],{"class":205},"  srcset",[116,1393,209],{"class":122},[116,1395,1396],{"class":212},"\"\u002Fimages\u002Fhero-400.jpg 400w, \u002Fimages\u002Fhero-800.jpg 800w, \u002Fimages\u002Fhero-1600.jpg 1600w\"\n",[116,1398,1399,1402,1404],{"class":118,"line":161},[116,1400,1401],{"class":205},"  sizes",[116,1403,209],{"class":122},[116,1405,1406],{"class":212},"\"(max-width: 600px) 100vw, 800px\"\n",[116,1408,1409,1412,1414],{"class":118,"line":388},[116,1410,1411],{"class":205},"  width",[116,1413,209],{"class":122},[116,1415,1416],{"class":212},"\"800\"\n",[116,1418,1419,1422,1424],{"class":118,"line":409},[116,1420,1421],{"class":205},"  height",[116,1423,209],{"class":122},[116,1425,1426],{"class":212},"\"450\"\n",[116,1428,1429,1432,1434],{"class":118,"line":626},[116,1430,1431],{"class":205},"  loading",[116,1433,209],{"class":122},[116,1435,1436],{"class":212},"\"lazy\"\n",[116,1438,1439,1442,1444],{"class":118,"line":646},[116,1440,1441],{"class":205},"  decoding",[116,1443,209],{"class":122},[116,1445,1446],{"class":212},"\"async\"\n",[116,1448,1449,1452,1454],{"class":118,"line":656},[116,1450,1451],{"class":205},"  alt",[116,1453,209],{"class":122},[116,1455,1456],{"class":212},"\"노트북 앞에서 코드를 작성하는 개발자\"\n",[116,1458,1459],{"class":118,"line":1254},[116,1460,1128],{"class":122},[420,1462,1463,1472],{},[423,1464,1465],{},[426,1466,1467,1469],{},[429,1468,973],{},[429,1470,1471],{},"하는 일",[438,1473,1474,1484,1497,1510],{},[426,1475,1476,1481],{},[443,1477,1478],{},[25,1479,1480],{},"loading=\"lazy\"",[443,1482,1483],{},"화면 근처에 올 때까지 이미지 다운로드를 미룸 (지연 로딩, Lazy Loading)",[426,1485,1486,1494],{},[443,1487,1488,102,1491],{},[25,1489,1490],{},"width",[25,1492,1493],{},"height",[443,1495,1496],{},"이미지가 뜨기 전에 자리를 미리 잡아서 화면이 덜컹거리지 않게 함 (CLS 감소)",[426,1498,1499,1507],{},[443,1500,1501,102,1504],{},[25,1502,1503],{},"srcset",[25,1505,1506],{},"sizes",[443,1508,1509],{},"화면 크기에 맞는 해상도만 골라서 다운로드",[426,1511,1512,1517],{},[443,1513,1514],{},[25,1515,1516],{},"alt",[443,1518,1519],{},"스크린 리더와 검색 엔진이 읽는 대체 텍스트",[1130,1521,1522],{},[80,1523,1524],{},[15,1525,1526,1527,1529,1530,1533,1534,1537],{},"첫 화면에 바로 보이는 큰 이미지(히어로 이미지)에는 ",[25,1528,1480],{},"를 ",[60,1531,1532],{},"붙이지 않는다."," 오히려 늦게 떠서 LCP(가장 큰 콘텐츠가 그려지는 시간) 점수가 나빠진다. 이런 이미지에는 ",[25,1535,1536],{},"fetchpriority=\"high\"","가 어울린다.",[93,1539],{},[10,1541,1543],{"id":1542},"_8-의미를-담는-작은-태그들","8. 의미를 담는 작은 태그들",[15,1545,1546,1547,1549,1550,1553],{},"모양만 보면 ",[25,1548,116],{},"에 CSS를 입힌 것과 다를 게 없지만, ",[60,1551,1552],{},"의미","를 담고 있어서 검색 엔진과 보조기기가 더 잘 이해한다.",[420,1555,1556,1568],{},[423,1557,1558],{},[426,1559,1560,1563,1565],{},[429,1561,1562],{},"태그",[429,1564,1552],{},[429,1566,1567],{},"예시",[438,1569,1570,1589,1606,1623,1638],{},[426,1571,1572,1577,1580],{},[443,1573,1574],{},[25,1575,1576],{},"\u003Ckbd>",[443,1578,1579],{},"키보드 입력",[443,1581,1582,102,1586],{},[1583,1584,1585],"kbd",{},"Ctrl",[1583,1587,1588],{},"C",[426,1590,1591,1596,1599],{},[443,1592,1593],{},[25,1594,1595],{},"\u003Cmark>",[443,1597,1598],{},"강조 표시(형광펜)",[443,1600,1601,1602],{},"검색 결과에서 ",[1603,1604,1605],"mark",{},"일치한 단어",[426,1607,1608,1613,1616],{},[443,1609,1610],{},[25,1611,1612],{},"\u003Cabbr>",[443,1614,1615],{},"약어 (마우스를 올리면 풀네임)",[443,1617,1618],{},[1619,1620,1622],"abbr",{"title":1621},"Cumulative Layout Shift","CLS",[426,1624,1625,1630,1633],{},[443,1626,1627],{},[25,1628,1629],{},"\u003Ctime>",[443,1631,1632],{},"기계가 읽을 수 있는 날짜",[443,1634,1635],{},[25,1636,1637],{},"\u003Ctime datetime=\"2026-10-08\">10월 8일\u003C\u002Ftime>",[426,1639,1640,1648,1651],{},[443,1641,1642,742,1645],{},[25,1643,1644],{},"\u003Cprogress>",[25,1646,1647],{},"\u003Cmeter>",[443,1649,1650],{},"진행률 \u002F 측정값",[443,1652,1653,1658,1659],{},[1654,1655],"progress",{"value":1656,"max":1657},"70","100"," ",[1660,1661],"meter",{"value":1662,"low":1663,"high":1664},"0.9",0.6,0.8,[15,1666,1667,1668,1670,1671,1673,1674,1676,1677,1680,1681,1684],{},"위 표의 키보드 키, 형광펜, ",[25,1669,1622],{},", 막대 두 개는 모두 실제 태그로 렌더링한 것이다. ",[25,1672,1622],{},"에 마우스를 올려보자. ",[25,1675,1660],{},"는 ",[25,1678,1679],{},"low","\u002F",[25,1682,1683],{},"high"," 기준을 넘으면 브라우저가 알아서 색을 바꿔준다 (위 예시는 90%라서 경고색).",[93,1686],{},[10,1688,1690,1691,1693],{"id":1689},"_9-div-대신-써야-할-시맨틱-태그-한눈에-보기","9. ",[25,1692,27],{}," 대신 써야 할 시맨틱 태그 한눈에 보기",[15,1695,1696],{},"시맨틱 태그(Semantic Tag)는 \"이 영역이 무슨 역할인지\"를 태그 이름으로 알려준다. 스크린 리더 사용자는 이걸로 \"본문으로 바로 이동\" 같은 탐색을 하고, 검색 엔진은 핵심 콘텐츠를 더 정확히 골라낸다.",[420,1698,1699,1709],{},[423,1700,1701],{},[426,1702,1703,1706],{},[429,1704,1705],{},"이렇게 쓰고 있다면",[429,1707,1708],{},"이렇게 바꾸자",[438,1710,1711,1723,1735,1748,1760,1772,1784],{},[426,1712,1713,1718],{},[443,1714,1715],{},[25,1716,1717],{},"\u003Cdiv class=\"header\">",[443,1719,1720],{},[25,1721,1722],{},"\u003Cheader>",[426,1724,1725,1730],{},[443,1726,1727],{},[25,1728,1729],{},"\u003Cdiv class=\"nav\">",[443,1731,1732],{},[25,1733,1734],{},"\u003Cnav>",[426,1736,1737,1742],{},[443,1738,1739],{},[25,1740,1741],{},"\u003Cdiv class=\"main\">",[443,1743,1744,1747],{},[25,1745,1746],{},"\u003Cmain>"," (페이지에 하나만)",[426,1749,1750,1755],{},[443,1751,1752],{},[25,1753,1754],{},"\u003Cdiv class=\"post\">",[443,1756,1757],{},[25,1758,1759],{},"\u003Carticle>",[426,1761,1762,1767],{},[443,1763,1764],{},[25,1765,1766],{},"\u003Cdiv class=\"sidebar\">",[443,1768,1769],{},[25,1770,1771],{},"\u003Caside>",[426,1773,1774,1779],{},[443,1775,1776],{},[25,1777,1778],{},"\u003Cdiv class=\"footer\">",[443,1780,1781],{},[25,1782,1783],{},"\u003Cfooter>",[426,1785,1786,1791],{},[443,1787,1788],{},[25,1789,1790],{},"\u003Cdiv onclick=\"...\">",[443,1792,1793],{},[25,1794,1795],{},"\u003Cbutton>",[15,1797,1798,1799,1801,1802,1805,1806,1808,1809,1811],{},"마지막 줄이 가장 중요하다. ",[25,1800,27],{},"에 클릭 이벤트를 달면 ",[60,1803,1804],{},"키보드로는 누를 수 없고",", 스크린 리더는 그게 버튼인지 모른다. 클릭되는 건 ",[25,1807,324],{},", 페이지를 이동하는 건 ",[25,1810,370],{},". 이것만 지켜도 접근성 문제의 상당수가 사라진다.",[93,1813],{},[10,1815,1817],{"id":1816},"정리-왜-알아두면-좋은가","정리: 왜 알아두면 좋은가",[420,1819,1820,1830],{},[423,1821,1822],{},[426,1823,1824,1827],{},[429,1825,1826],{},"만들고 싶은 것",[429,1828,1829],{},"HTML 해결책",[438,1831,1832,1847,1858,1869,1878,1887,1900],{},[426,1833,1834,1837],{},[443,1835,1836],{},"FAQ 아코디언",[443,1838,1839,102,1841,1843,1844,1846],{},[25,1840,101],{},[25,1842,105],{}," (+ ",[25,1845,183],{},")",[426,1848,1849,1852],{},[443,1850,1851],{},"드롭다운 \u002F 툴팁",[443,1853,1854,102,1856],{},[25,1855,305],{},[25,1857,450],{},[426,1859,1860,1863],{},[443,1861,1862],{},"확인 모달",[443,1864,1865,102,1867],{},[25,1866,503],{},[25,1868,669],{},[426,1870,1871,1874],{},[443,1872,1873],{},"검색어 추천",[443,1875,1876],{},[25,1877,783],{},[426,1879,1880,1883],{},[443,1881,1882],{},"모바일 숫자 키패드",[443,1884,1885],{},[25,1886,1017],{},[426,1888,1889,1892],{},[443,1890,1891],{},"입력값 검사",[443,1893,1894,1282,1896,102,1898],{},[25,1895,1281],{},[25,1897,1297],{},[25,1899,1304],{},[426,1901,1902,1905],{},[443,1903,1904],{},"이미지 성능",[443,1906,1907,1282,1909,1680,1911,1282,1913],{},[25,1908,1480],{},[25,1910,1490],{},[25,1912,1493],{},[25,1914,1503],{},[15,1916,1917],{},"브라우저 기본 기능을 쓰면 세 가지를 동시에 얻는다.",[1919,1920,1921,1927,1933],"ol",{},[22,1922,1923,1926],{},[60,1924,1925],{},"코드가 준다."," 상태 관리, 이벤트 처리, 바깥 클릭 감지 같은 코드를 지워도 된다.",[22,1928,1929,1932],{},[60,1930,1931],{},"접근성이 따라온다."," 키보드 조작, 포커스 관리, 스크린 리더 안내를 브라우저가 이미 해준다.",[22,1934,1935,1938],{},[60,1936,1937],{},"번들이 가벼워진다."," UI 라이브러리를 하나 덜 설치해도 된다.",[15,1940,1941,1942,1945],{},"물론 디자인을 아주 세밀하게 바꿔야 하거나 복잡한 동작이 필요하면 직접 만들거나 라이브러리를 쓰는 게 맞다. 다만 그 결정을 내리기 전에 ",[60,1943,1944],{},"\"HTML에 이미 있나?\"를 먼저 확인하는 습관","만 들여도 프로젝트가 훨씬 가벼워진다.",[1947,1948,1949],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":112,"searchDepth":132,"depth":132,"links":1951},[1952,1953,1955,1957,1959,1961,1963,1964,1965,1966,1968],{"id":12,"depth":132,"text":13},{"id":97,"depth":132,"text":1954},"1. 아코디언: \u003Cdetails> + \u003Csummary>",{"id":301,"depth":132,"text":1956},"2. 팝오버: popover 속성",{"id":499,"depth":132,"text":1958},"3. 모달: \u003Cdialog>",{"id":779,"depth":132,"text":1960},"4. 자동완성: \u003Cdatalist>",{"id":954,"depth":132,"text":1962},"5. 모바일 키보드까지 챙기는 \u003Cinput> 속성",{"id":1151,"depth":132,"text":1152},{"id":1363,"depth":132,"text":1364},{"id":1542,"depth":132,"text":1543},{"id":1689,"depth":132,"text":1967},"9. div 대신 써야 할 시맨틱 태그 한눈에 보기",{"id":1816,"depth":132,"text":1817},"아코디언, 팝오버, 모달, 자동완성, 폼 검증, 이미지 지연 로딩까지. 라이브러리 없이 HTML 태그와 속성만으로 해결되는 기능을 예제와 함께 정리했다. 코드는 줄고 접근성은 덤으로 따라온다.","md",{"date":1972,"field":1973,"tags":1974},"2026.10.08","frontend",[111],"\u002Fko\u002Fhtml-features-without-javascript",{"title":5,"description":1969},{"loc":1975},"ko\u002Fhtml-features-without-javascript","9gviwOaTSR50scMIuJgMaPuxTAj--ThR3MTo3L4C2cE",[1981,1992,1998,2006,2013,2021,2028,2039,2047,2055,2067,2075,2084,2090,2093,2100,2107,2114,2121,2127,2136,2145,2151,2158,2164,2171,2178,2186,2193,2200,2209,2216,2223,2230,2236,2244,2251,2259,2265],{"path":1982,"title":1983,"description":1984,"meta":1985},"\u002Fko\u002Fadsense-ads-txt-and-seo-indexing-bug","애드센스 ads.txt 경고를 받고 파헤치다가 찾은 진짜 색인 버그","구글 애드센스가 ads.txt 문제를 알려왔는데, 확인해보니 파일은 멀쩡했다. 대신 서치콘솔 색인 수가 적었던 진짜 원인을 Nuxt Content 슬러그 불일치 버그에서 찾은 과정을 정리한다.",{"date":1986,"field":1987,"tags":1988},"2026.09.17","blog",[1989,1990,1991],"seo","adsense","nuxt-content",{"path":1993,"title":1994,"description":1995,"meta":1996},"\u002Fko\u002Fcss-has-selector","CSS :has() 선택자 실전 가이드: 자바스크립트 없이 부모를 선택하는 법","체크하면 카드가 강조되고, 입력이 틀리면 폼 전체가 반응하고, 별점에 마우스를 올리면 앞의 별까지 칠해진다. 예전엔 자바스크립트가 필요했던 일을 CSS :has() 한 줄로 해결하는 실전 예제를 직접 눌러보며 정리했다.",{"date":1972,"field":1973,"tags":1997},[705],{"path":1999,"title":2000,"description":2001,"meta":2002},"\u002Fko\u002Fcss-layouts","CSS Flexbox 완전 정복 가이드","Flexbox를 활용한 반응형 레이아웃 구현 방법과 실전 예제. 수직 정렬, 네비게이션 바, 카드 레이아웃까지 완벽 정리!",{"date":2003,"image":2004,"field":1973,"tags":2005},"2025.01.25","css.jpg",[705],{"path":2007,"title":2008,"description":2009,"meta":2010},"\u002Fko\u002Fcss-login-form","로그인","로그인 UI를 그려보자",{"date":2011,"field":1973,"tags":2012},"2025.03.29",[111,705],{"path":2014,"title":2015,"description":2016,"meta":2017},"\u002Fko\u002Fcsv-parser-cleaning","복잡한 CSV 데이터를 안전하게 파싱하는 방법","JavaScript에서 큰따옴표, 줄바꿈, 쉼표 등 복잡한 CSV 데이터를 안전하게 객체로 변환하는 전체 과정 설명",{"date":2018,"field":1973,"tags":2019},"2025.06.19",[2020],"javascript",{"path":2022,"title":2023,"description":2024,"meta":2025},"\u002Fko\u002Fdebounce-guide","debounce와 throttle이란??","반복되는 함수 호출을 제어하는 기술인 디바운스(debounce)와 쓰로틀(throttle)을 알아보자.",{"date":2026,"field":1973,"tags":2027},"2025.03.26",[2020],{"path":2029,"title":2030,"description":2031,"meta":2032},"\u002Fko\u002Fdeclarative-vs-procedural-programming","선언적 프로그래밍 vs 절차형 프로그래밍","선언적 프로그래밍과 절차형 프로그래밍은 서로 대비되는 대표적인 패러다임이다. 두 패러다임의 차이를 알아보고, 각 패러다임에 해당하는 언어와 Vue\u002FReact의 특징을 살펴보자.",{"date":2033,"field":2034,"tags":2035},"2025.05.24","concepts",[2036,2037,2038],"paradigm","vue","react",{"path":2040,"title":2041,"description":2042,"meta":2043},"\u002Fko\u002Fdesign-guide","Design-Guide","A comprehensive guide on design principles and best practices for developers.",{"date":2044,"field":1973,"tags":2045},"2025.01.19",[2046],"design-system",{"path":2048,"title":2049,"description":2050,"meta":2051},"\u002Fko\u002Fdikw-pyramid","DIKW 피라미드 정리: 데이터가 지혜가 되기까지","Data, Information, Knowledge, Wisdom 4단계로 구성된 DIKW 피라미드 개념을 로그\u002F분석 데이터 같은 실제 개발 사례에 대입해서 정리한다.",{"date":2052,"field":2034,"tags":2053},"2026.09.18",[2054],"data",{"path":2056,"title":2057,"description":2058,"meta":2059},"\u002Fko\u002Fdisplaying-commits-with-github-actions","깃허브 액션으로 커밋 로그를 블로그에 띄우기: Nuxt와 Vercel 활용법","깃허브 액션을 통해 커밋 데이터를 자동으로 수집하고, Nuxt와 Vercel로 UI에 노출하는 방법을 소개합니다.",{"date":2060,"image":2061,"field":2062,"tags":2063},"2025.02.28","github-commits.jpg","infra",[2064,2065,2066],"github-actions","vercel","nuxt",{"path":2068,"title":2069,"description":2070,"meta":2071},"\u002Fko\u002Fdocker-auto-build-run","로컬에서 커밋 시 도커 빌드 후 컨테이너 시작하기","로컬에서 커밋 시 도커 빌드 후 컨테이너 시작하기.",{"date":2072,"field":2062,"tags":2073},"2025.03.10",[2074],"docker",{"path":2076,"title":2077,"description":2078,"meta":2079},"\u002Fko\u002Fhow-to-mount-storage","라즈베리파이4에 저장장치 마운트 시키기","라즈베리파이에 저장장치를 연결하고 파티션 생성부터 포맷, 마운트까지의 전체 과정을 정리한 기록입니다.",{"date":2080,"field":2062,"tags":2081},"2025.07.11",[2082,2083],"raspberry-pi","linux",{"path":2085,"title":2086,"description":2086,"meta":2087},"\u002Fko\u002Fhtml","HTML이란 무엇인가",{"date":2003,"image":2088,"field":1973,"tags":2089},"html.jpg",[111],{"path":1975,"title":5,"description":1969,"meta":2091},{"date":1972,"field":1973,"tags":2092},[111],{"path":2094,"title":2095,"description":2096,"meta":2097},"\u002Fko\u002Fhtml2","미치광이 카드 컴포넌트","미치광이 카드 컴포넌트는 어떤 느낌일까",{"date":2098,"field":1973,"tags":2099},"2025.03.28",[111,705],{"path":2101,"title":2102,"description":2102,"meta":2103},"\u002Fko\u002Fhttps-guide","Let's Encrypt를 이용해서 HTTPS 적용하기",{"date":2104,"field":2062,"tags":2105},"2025.03.11",[2106],"https",{"path":2108,"title":2109,"description":2110,"meta":2111},"\u002Fko\u002Fjavascript-event-loop","자바스크립트 이벤트 루프 완전 정복: 마이크로태스크와 매크로태스크의 실행 순서","setTimeout(0)보다 Promise.then이 먼저 실행되는 이유는? 콜 스택, 태스크 큐, 마이크로태스크 큐가 어떤 순서로 돌아가는지 예제로 정리하고, 브라우저와 Node.js의 차이, 실무에서 UI 멈춤을 피하는 방법까지 알아보자.",{"date":1972,"field":2034,"tags":2112},[2020,2113],"nodejs",{"path":2115,"title":2116,"description":2117,"meta":2118},"\u002Fko\u002Fjs-data-01","JavaScript 데이터01","JavaScript 데이터 다루는 문제를 풀어보자",{"date":2119,"field":1973,"tags":2120},"2025.03.08",[2020],{"path":2122,"title":2123,"description":2124,"meta":2125},"\u002Fko\u002Fjs-data-02","JavaScript 데이터02","복잡한 데이터 구조를 다루는 실전 문제를 해결해보자.",{"date":2119,"field":1973,"tags":2126},[2020],{"path":2128,"title":2129,"description":2130,"meta":2131},"\u002Fko\u002Fmarkdown-guid","마크다운 문법","마크다운 문법을 알아보자",{"date":2132,"image":2133,"field":1987,"tags":2134},"2025.03.02","markdown.webp",[2135],"markdown",{"path":2137,"title":2138,"description":2139,"meta":2140},"\u002Fko\u002Fminio-guide","MinIO 완벽 가이드: 설치부터 Node.js 연동까지","MinIO는 오픈소스 기반의 고성능 객체 스토리지 서버입니다. AWS S3와 호환되는 API를 제공하여 클라우드 네이티브 애플리케이션을 위한 완벽한 스토리지 솔루션으로 자리잡고 있습니다.",{"date":2141,"field":2142,"tags":2143},"2025.11.23","backend",[2144,2113],"minio",{"path":2146,"title":2147,"description":2148,"meta":2149},"\u002Fko\u002Fnitro-wildcard-redirect-url-migration","글 주소 프리픽스를 통째로 바꿔야 할 때: Nitro wildcard 리다이렉트로 안전하게 옮기기","\u002Fblog\u002F[슬러그]로 쓰던 글 주소를 \u002Fko\u002F[슬러그]로 옮기면서, 기존 링크\u002F북마크를 하나도 안 깨뜨리고 처리한 방법. Nitro routeRules의 와일드카드 리다이렉트 패턴과 실제로 같이 바꿔야 했던 범위를 정리한다.",{"date":1972,"field":1987,"tags":2150},[2066,1989],{"path":2152,"title":2153,"description":2154,"meta":2155},"\u002Fko\u002Fnode-simple-server","Node.js 간단한 서버 만들기","Node.js로 간단한 HTTP 서버를 만드는 방법",{"date":2156,"field":2142,"tags":2157},"2025.01.30",[2113],{"path":2159,"title":2160,"description":2161,"meta":2162},"\u002Fko\u002Fnuxt-colormode","Nuxt3 다크모드 적용하기","Nuxt3에 다크모드 적용하여 관리하기",{"date":2011,"field":1973,"tags":2163},[2066],{"path":2165,"title":2166,"description":2167,"meta":2168},"\u002Fko\u002Fnuxt-content-vercel-empty-query-sqlite","배포만 하면 블로그 글이 하나도 안 보이던 버그: Nuxt Content와 Vercel의 \u002Ftmp 문제","로컬에서는 멀쩡한데 Vercel에 올리면 queryCollection()이 빈 배열만 반환하던 버그를 추적한 과정. 원인은 @nuxt\u002Fcontent가 Vercel의 읽기 전용 파일시스템을 지원하기 전 버전에 고정돼 있었던 것.",{"date":2169,"field":1987,"tags":2170},"2026.10.07",[1991,2065],{"path":2172,"title":2173,"description":2174,"meta":2175},"\u002Fko\u002Fnuxt-runtimeconfig-appconfig","Nuxt 설정 runtimeConfig vs app.config 완전 정복","Nuxt3에서 전역 설정을 어떻게 관리하고, 어떤 기준으로 나누어야 할까? 다크모드 예시부터 실무 기준 설정 전략까지 총정리",{"date":2176,"field":1973,"tags":2177},"2025.06.16",[2066],{"path":2179,"title":2180,"description":2181,"meta":2182},"\u002Fko\u002Fnuxt-useasyncdata-cache-error","useAsyncData 캐싱 문제와 Key 동적 설정으로 해결하기","이전 포스트의 데이터가 계속 출력되는 캐싱 관련 문제를 해결하는 방법",{"date":2183,"field":1973,"tags":2184,"image":2185},"2025.01.24",[2066],"cache.jpg",{"path":2187,"title":2188,"description":2189,"meta":2190},"\u002Fko\u002Fnuxt-vercel-sql-error","Nuxt 3에서 Vercel 배포 시 발생한 md 파일 로딩 문제 해결기","Nuxt 3 웹 어플리케이션에서 Vercel 배포 환경에서 발생한 md 파일 로딩 문제를 분석하고 해결한 방법에 대해 공유합니다.",{"date":2183,"image":2191,"field":1973,"tags":2192},"ssg.jpg",[2066,1991,2065],{"path":2194,"title":2195,"description":2196,"meta":2197},"\u002Fko\u002Fnuxt3-pinia-guide","nuxt3 Pinia 적용하기","Nuxt3 프로젝트에서 Pinia를 적용해보자 @pinia\u002Fnuxt",{"date":2011,"field":1973,"tags":2198},[2066,2199],"pinia",{"path":2201,"title":2202,"description":2203,"meta":2204},"\u002Fko\u002Fnuxt3-unit-test","Nuxt 프로젝트에 유닛 테스트 코드 추가하기","Nuxt 3 프로젝트에서 Vitest를 활용한 테스트 환경 설정과 유틸리티 함수 테스트 방법",{"date":2205,"image":2206,"field":1973,"tags":2207},"2025.03.07","unit-test.jpg",[2066,2208],"vitest",{"path":2210,"title":2211,"description":2212,"meta":2213},"\u002Fko\u002Fpm2-guide","프로세스 매니저 PM2","배포환경에서 유용하게 사용할 수 있는 프로세스 매니저를 알아보자.",{"date":2072,"field":2062,"tags":2214},[2215,2113],"pm2",{"path":2217,"title":2218,"description":2219,"meta":2220},"\u002Fko\u002Freact-createportal","React createPortal 완벽 가이드","React의 `createPortal`을 활용한 UI 구현법, 활용 사례, 장단점 및 코드 예제를 소개합니다.",{"date":2221,"field":1973,"tags":2222},"2025.01.27",[2038],{"path":2224,"title":2225,"description":2226,"meta":2227},"\u002Fko\u002Frouting","Routing","Nuxt file-system routing creates a route for every file in the pages\u002F directory.",{"date":2228,"field":1973,"tags":2229},"2025.01.22",[2066],{"path":2231,"title":2232,"description":2233,"meta":2234},"\u002Fko\u002Fsemantic-html-common-mistakes","의미 있는 HTML 쓰기: 대부분이 하는 시맨틱 HTML 실수 7가지","화면에서는 똑같아 보여도 div로 만든 버튼, label 없는 입력칸, 크기로 고른 제목 태그는 키보드 사용자와 스크린 리더, 검색 엔진에게 전혀 다르게 보인다. 직접 눌러볼 수 있는 예제로 흔한 시맨틱 HTML 실수와 고치는 법을 정리했다.",{"date":1972,"field":1973,"tags":2235},[111],{"path":2237,"title":2238,"description":2239,"meta":2240},"\u002Fko\u002Ftanstack-query-guide","TanStack Query란 무엇인가?","TanStack Query가 웹 애플리케이션에서 데이터 가져오기, 캐싱, 상태 관리를 어떻게 개선하는지 알아본다.",{"date":2241,"field":1973,"tags":2242},"2025.04.15",[2038,2243],"tanstack-query",{"path":2245,"title":2246,"description":2247,"meta":2248},"\u002Fko\u002Fuse-context","리엑트 상태관리 useContext()","React에서 `useContext`를 사용하면 컴포넌트 트리에 깊이 중첩된 컴포넌트에서도 prop drilling 없이 상태를 공유할 수 있습니다. 이 글에서는 `useContext`의 개념, 사용 방법, 장단점, 그리고 실전 예제를 함께 알아보겠습니다.",{"date":2249,"field":1973,"tags":2250},"2025.02.03",[2038],{"path":2252,"title":2253,"description":2254,"meta":2255},"\u002Fko\u002Fvite-plugin-style-inject","리엑티 컴포넌트를 패키지 빌드 시 CSS 같이 적용시키기","dist에 css에 파일이 생성되었으나, 적용 안 되는 이유를 알아보고 에러를 처리하자",{"date":2256,"field":1973,"tags":2257},"2025.04.06",[2258,2038],"vite",{"path":2260,"title":2261,"description":2262,"meta":2263},"\u002Fko\u002Fvue-component-guide","Vue의 \u003Ccomponent :is \u002F> 완벽 정리: 동적 렌더링의 모든 것","Vue에서 \u003Ccomponent :is \u002F>를 활용한 동적 컴포넌트 렌더링 방법과 VNode, 컴포넌트 객체의 차이를 실전 예제로 이해하기",{"date":2003,"field":1973,"tags":2264},[2037],{"path":2266,"title":2267,"description":2268,"meta":2269},"\u002Fko\u002Fvue-query-ref-value-trap-nuxt3","Vue Query에서 데이터가 안 보이는 이유? Nuxt 3와 Ref 구조의 함정","Nuxt 3 + vue-query를 사용할 때 API 데이터가 `v-if` 조건문에서 제대로 표시되지 않는 현상을 해결한 경험을 바탕으로, Ref 구조와 template 디렉티브에서의 차이를 자세히 설명합니다.",{"date":2270,"field":1973,"tags":2271},"2025.06.08",[2066,2037,2243],1791457737952]