[{"data":1,"prerenderedAt":2254},["ShallowReactive",2],{"page-\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Ffocus-within-form-patterns\u002F":3,"content-all-pages":1992},{"id":4,"title":5,"body":6,"description":1971,"extension":1972,"meta":1973,"navigation":666,"path":1988,"seo":1989,"stem":1990,"__hash__":1991},"content\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Ffocus-within-form-patterns\u002Findex.md"," Form Patterns: Styling a Group From the Field Inside It",{"type":7,"value":8,"toc":1960},"minimark",[9,16,55,61,82,90,112,115,134,233,254,258,261,265,526,1082,1093,1097,1107,1110,1187,1627,1641,1645,1654,1674,1678,1704,1801,1810,1814,1836,1840,1863,1877,1899,1919,1923,1956],[10,11,13,5],"h1",{"id":12},"form-patterns-styling-a-group-from-the-field-inside-it",[14,15],"focus-within",{},[17,18,19,20,24,25,28,29,32,33,36,37,39,40,43,44,49,50,54],"p",{},"Forms are the one place where the focused element is almost never the element you want to style. Focus lands on an ",[21,22,23],"code",{},"\u003Cinput>","; the thing that should react is the field group around it, the label beside it, or the help text below it. Before ",[21,26,27],{},":focus-within"," this required a ",[21,30,31],{},"focusin","\u002F",[21,34,35],{},"focusout"," listener toggling a class, and every stale class was a visual bug. The narrow problem this page solves is expressing all of those group-level reactions in CSS, and knowing precisely when ",[21,38,27],{}," is the right selector and when ",[21,41,42],{},":has(:focus)"," is. It belongs to ",[45,46,48],"a",{"href":47},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002F","Hover & Focus State Design"," and complements the input-capability work in ",[45,51,53],{"href":52},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Fpointer-and-hover-media-queries\u002F","Pointer and Hover Media Queries",".",[17,56,57],{},[58,59,60],"strong",{},"Patterns covered:",[62,63,64,68,71,74],"ul",{},[65,66,67],"li",{},"Highlighting the active field group",[65,69,70],{},"A floating label with no JavaScript",[65,72,73],{},"Revealing help text only while the group is in use",[65,75,76,77,79,80],{},"Choosing between ",[21,78,27],{}," and ",[21,81,42],{},[83,84,86,87,89],"h2",{"id":85},"what-focus-within-actually-matches","What ",[21,88,27],{}," actually matches",[17,91,92,94,95,98,99,102,103,105,106,108,109,111],{},[21,93,27],{}," matches an element if that element matches ",[21,96,97],{},":focus",", ",[58,100,101],{},"or"," if any element in its flat-tree descendants matches ",[21,104,97],{},". Two details in that definition do real work. First, the element itself counts — a focusable container matches its own ",[21,107,27],{},". Second, the flat tree is what is traversed, so focus inside a shadow root propagates up through the host: a custom element wrapping a native input will match ",[21,110,27],{}," when that input is focused, which a naive descendant selector could not express.",[17,113,114],{},"There is no upper bound on the distance. Focus five levels deep still matches every ancestor along the chain, which is why the selector is normally scoped to a specific container class rather than applied broadly.",[17,116,117,119,120,124,125,127,128,130,131,54],{},[21,118,42],{}," looks equivalent and is not. It matches an element that ",[121,122,123],"em",{},"contains"," a focused element, and stops there: the focused element itself does not match ",[21,126,42],{},", because nothing inside it is focused. In practice ",[21,129,27],{}," is equivalent to ",[21,132,133],{},":is(:focus, :has(:focus))",[135,136,142,143,142,147,142,151,142,158,142,170,142,175,142,184,142,189,142,197,142,201,142,206,142,209,142,212,142,215,142,217,142,223,142,225,142,228],"svg",{"viewBox":137,"role":138,"ariaLabel":139,"xmlns":140,"style":141},"0 0 720 330","img","Comparison of which elements focus-within and has-focus match in the same tree","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[144,145,146],"title",{},"focus-within compared with has(:focus)",[148,149,150],"desc",{},"Two identical trees show that both selectors match the containing group, and that only focus-within also matches a focused element itself.",[152,153,157],"text",{"x":154,"y":155,"style":156},"360","26","text-anchor:middle;fill:currentColor;font:700 17px sans-serif","Same tree, two selectors",[159,160],"rect",{"x":161,"y":162,"width":163,"height":164,"rx":165,"fill":166,"stroke":167,"strokeWidth":168,"opacity":169},"30","48","320","238","10","none","currentColor","1.5","0.5",[152,171,27],{"x":172,"y":173,"style":174},"190","78","text-anchor:middle;fill:currentColor;font:600 14px ui-monospace,monospace",[159,176],{"x":177,"y":178,"width":179,"height":180,"rx":181,"fill":182,"opacity":183},"70","96","240","128","8","#7aa2ff","0.22",[152,185,188],{"x":172,"y":186,"style":187},"120","text-anchor:middle;fill:currentColor;font:12px ui-monospace,monospace",".group",[159,190],{"x":191,"y":192,"width":193,"height":194,"rx":195,"fill":182,"opacity":196},"100","134","180","46","6","0.34",[152,198,200],{"x":172,"y":199,"style":187},"162","input:focus",[152,202,205],{"x":172,"y":203,"style":204},"252","text-anchor:middle;fill:currentColor;font:12px sans-serif;opacity:0.85","matches .group and input",[159,207],{"x":208,"y":162,"width":163,"height":164,"rx":165,"fill":166,"stroke":167,"strokeWidth":168,"opacity":169},"370",[152,210,42],{"x":211,"y":173,"style":174},"530",[159,213],{"x":214,"y":178,"width":179,"height":180,"rx":181,"fill":182,"opacity":183},"410",[152,216,188],{"x":211,"y":186,"style":187},[159,218],{"x":219,"y":192,"width":193,"height":194,"rx":195,"fill":166,"stroke":167,"strokeWidth":168,"strokeDashArray":220},"440",[221,222],"5","4",[152,224,200],{"x":211,"y":199,"style":187},[152,226,227],{"x":211,"y":203,"style":204},"matches .group only",[152,229,232],{"x":154,"y":230,"style":231},"312","text-anchor:middle;fill:currentColor;font:12px sans-serif;opacity:0.7","A focused element matches its own :focus-within, but never its own :has(:focus).",[17,234,235,236,238,239,242,243,246,247,250,251,253],{},"The other difference is weight. ",[21,237,27],{}," is an ordinary pseudo-class contributing ",[21,240,241],{},"(0, 1, 0)"," to specificity. ",[21,244,245],{},":has()"," contributes nothing itself but takes the specificity of its most specific argument, so ",[21,248,249],{},".group:has(#save:focus)"," inherits an ID's weight and will beat rules you did not expect it to. When the two selectors would do the same job, ",[21,252,27],{}," is the predictable one.",[83,255,257],{"id":256},"pattern-1-the-active-field-group","Pattern 1: the active field group",[17,259,260],{},"Tab or click into either input — the whole group takes a ring and its help text appears.",[262,263],"live-demo",{"name":264},"hf-focus-within-fieldgroup",[266,267,272],"pre",{"className":268,"code":269,"language":270,"meta":271,"style":271},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast","\u003Cform class=\"form\">\n  \u003Cfieldset class=\"group\">\n    \u003Clegend class=\"group__legend\">Card details\u003C\u002Flegend>\n    \u003Clabel class=\"field\">\u003Cspan class=\"field__label\">Number\u003C\u002Fspan>\n      \u003Cinput class=\"field__input\" type=\"text\" inputmode=\"numeric\" placeholder=\"4242 4242 4242 4242\">\u003C\u002Flabel>\n    \u003Clabel class=\"field\">\u003Cspan class=\"field__label\">Expiry\u003C\u002Fspan>\n      \u003Cinput class=\"field__input\" type=\"text\" placeholder=\"MM \u002F YY\">\u003C\u002Flabel>\n    \u003Cp class=\"group__help\">We never store the full number — only the last four digits.\u003C\u002Fp>\n  \u003C\u002Ffieldset>\n\u003C\u002Fform>\n","html","",[21,273,274,301,319,342,376,423,453,485,506,516],{"__ignoreMap":271},[275,276,279,283,287,291,294,298],"span",{"class":277,"line":278},"line",1,[275,280,282],{"class":281},"suds8","\u003C",[275,284,286],{"class":285},"sne4z","form",[275,288,290],{"class":289},"s-5SL"," class",[275,292,293],{"class":281},"=",[275,295,297],{"class":296},"sT6z2","\"form\"",[275,299,300],{"class":281},">\n",[275,302,304,307,310,312,314,317],{"class":277,"line":303},2,[275,305,306],{"class":281},"  \u003C",[275,308,309],{"class":285},"fieldset",[275,311,290],{"class":289},[275,313,293],{"class":281},[275,315,316],{"class":296},"\"group\"",[275,318,300],{"class":281},[275,320,322,325,328,330,332,335,338,340],{"class":277,"line":321},3,[275,323,324],{"class":281},"    \u003C",[275,326,327],{"class":285},"legend",[275,329,290],{"class":289},[275,331,293],{"class":281},[275,333,334],{"class":296},"\"group__legend\"",[275,336,337],{"class":281},">Card details\u003C\u002F",[275,339,327],{"class":285},[275,341,300],{"class":281},[275,343,345,347,350,352,354,357,360,362,364,366,369,372,374],{"class":277,"line":344},4,[275,346,324],{"class":281},[275,348,349],{"class":285},"label",[275,351,290],{"class":289},[275,353,293],{"class":281},[275,355,356],{"class":296},"\"field\"",[275,358,359],{"class":281},">\u003C",[275,361,275],{"class":285},[275,363,290],{"class":289},[275,365,293],{"class":281},[275,367,368],{"class":296},"\"field__label\"",[275,370,371],{"class":281},">Number\u003C\u002F",[275,373,275],{"class":285},[275,375,300],{"class":281},[275,377,379,382,385,387,389,392,395,397,400,403,405,408,411,413,416,419,421],{"class":277,"line":378},5,[275,380,381],{"class":281},"      \u003C",[275,383,384],{"class":285},"input",[275,386,290],{"class":289},[275,388,293],{"class":281},[275,390,391],{"class":296},"\"field__input\"",[275,393,394],{"class":289}," type",[275,396,293],{"class":281},[275,398,399],{"class":296},"\"text\"",[275,401,402],{"class":289}," inputmode",[275,404,293],{"class":281},[275,406,407],{"class":296},"\"numeric\"",[275,409,410],{"class":289}," placeholder",[275,412,293],{"class":281},[275,414,415],{"class":296},"\"4242 4242 4242 4242\"",[275,417,418],{"class":281},">\u003C\u002F",[275,420,349],{"class":285},[275,422,300],{"class":281},[275,424,426,428,430,432,434,436,438,440,442,444,446,449,451],{"class":277,"line":425},6,[275,427,324],{"class":281},[275,429,349],{"class":285},[275,431,290],{"class":289},[275,433,293],{"class":281},[275,435,356],{"class":296},[275,437,359],{"class":281},[275,439,275],{"class":285},[275,441,290],{"class":289},[275,443,293],{"class":281},[275,445,368],{"class":296},[275,447,448],{"class":281},">Expiry\u003C\u002F",[275,450,275],{"class":285},[275,452,300],{"class":281},[275,454,456,458,460,462,464,466,468,470,472,474,476,479,481,483],{"class":277,"line":455},7,[275,457,381],{"class":281},[275,459,384],{"class":285},[275,461,290],{"class":289},[275,463,293],{"class":281},[275,465,391],{"class":296},[275,467,394],{"class":289},[275,469,293],{"class":281},[275,471,399],{"class":296},[275,473,410],{"class":289},[275,475,293],{"class":281},[275,477,478],{"class":296},"\"MM \u002F YY\"",[275,480,418],{"class":281},[275,482,349],{"class":285},[275,484,300],{"class":281},[275,486,488,490,492,494,496,499,502,504],{"class":277,"line":487},8,[275,489,324],{"class":281},[275,491,17],{"class":285},[275,493,290],{"class":289},[275,495,293],{"class":281},[275,497,498],{"class":296},"\"group__help\"",[275,500,501],{"class":281},">We never store the full number — only the last four digits.\u003C\u002F",[275,503,17],{"class":285},[275,505,300],{"class":281},[275,507,509,512,514],{"class":277,"line":508},9,[275,510,511],{"class":281},"  \u003C\u002F",[275,513,309],{"class":285},[275,515,300],{"class":281},[275,517,519,522,524],{"class":277,"line":518},10,[275,520,521],{"class":281},"\u003C\u002F",[275,523,286],{"class":285},[275,525,300],{"class":281},[266,527,531],{"className":528,"code":529,"language":530,"meta":271,"style":271},"language-css shiki shiki-themes github-light-high-contrast github-dark-high-contrast",".group {\n  display: grid;\n  gap: 0.7rem;\n  margin: 0;\n  padding: 1rem;\n  border: 1px solid #cbd5e1;\n  border-radius: 10px;\n  transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n\n.field { display: grid; gap: 0.25rem; }\n\n.field__input {\n  padding: 0.5rem 0.6rem;\n  border: 1px solid #cbd5e1;\n  border-radius: 6px;\n  font: inherit;\n}\n\n\u002F* The fieldset is never itself focusable, so :focus matching it is\n   impossible — the group reacts entirely to its descendants. *\u002F\n.group:focus-within {\n  border-color: #2d5bff;\n  box-shadow: 0 0 0 3px rgb(45 91 255 \u002F 0.25);\n}\n\n\u002F* Reserve the help text's space at all times and animate only opacity,\n   so revealing it cannot reflow the form while the user is tabbing. *\u002F\n.group__help {\n  margin: 0;\n  font-size: 0.8rem;\n  opacity: 0;\n  transition: opacity 0.2s ease;\n}\n\n.group:focus-within .group__help { opacity: 1; }\n\n\u002F* The group ring is decorative context; the input keeps its own real\n   indicator so the focused field is still individually identifiable. *\u002F\n.field__input:focus-visible {\n  outline: 2px solid #2d5bff;\n  outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .group, .group__help { transition-duration: 0.01ms; }\n}\n","css",[21,532,533,540,554,570,582,596,616,629,657,662,668,700,705,713,731,748,761,774,779,784,791,797,805,818,861,866,871,877,883,891,902,917,929,945,950,955,974,979,985,991,999,1019,1033,1038,1043,1052,1077],{"__ignoreMap":271},[275,534,535,537],{"class":277,"line":278},[275,536,188],{"class":289},[275,538,539],{"class":281}," {\n",[275,541,542,545,548,551],{"class":277,"line":303},[275,543,544],{"class":289},"  display",[275,546,547],{"class":281},": ",[275,549,550],{"class":289},"grid",[275,552,553],{"class":281},";\n",[275,555,556,559,561,564,568],{"class":277,"line":321},[275,557,558],{"class":289},"  gap",[275,560,547],{"class":281},[275,562,563],{"class":289},"0.7",[275,565,567],{"class":566},"sHUrx","rem",[275,569,553],{"class":281},[275,571,572,575,577,580],{"class":277,"line":344},[275,573,574],{"class":289},"  margin",[275,576,547],{"class":281},[275,578,579],{"class":289},"0",[275,581,553],{"class":281},[275,583,584,587,589,592,594],{"class":277,"line":378},[275,585,586],{"class":289},"  padding",[275,588,547],{"class":281},[275,590,591],{"class":289},"1",[275,593,567],{"class":566},[275,595,553],{"class":281},[275,597,598,601,603,605,608,611,614],{"class":277,"line":425},[275,599,600],{"class":289},"  border",[275,602,547],{"class":281},[275,604,591],{"class":289},[275,606,607],{"class":566},"px",[275,609,610],{"class":289}," solid",[275,612,613],{"class":289}," #cbd5e1",[275,615,553],{"class":281},[275,617,618,621,623,625,627],{"class":277,"line":455},[275,619,620],{"class":289},"  border-radius",[275,622,547],{"class":281},[275,624,165],{"class":289},[275,626,607],{"class":566},[275,628,553],{"class":281},[275,630,631,634,637,640,643,646,649,651,653,655],{"class":277,"line":487},[275,632,633],{"class":289},"  transition",[275,635,636],{"class":281},": border-color ",[275,638,639],{"class":289},"0.2",[275,641,642],{"class":566},"s",[275,644,645],{"class":289}," ease",[275,647,648],{"class":281},", box-shadow ",[275,650,639],{"class":289},[275,652,642],{"class":566},[275,654,645],{"class":289},[275,656,553],{"class":281},[275,658,659],{"class":277,"line":508},[275,660,661],{"class":281},"}\n",[275,663,664],{"class":277,"line":518},[275,665,667],{"emptyLinePlaceholder":666},true,"\n",[275,669,671,674,677,680,682,684,687,690,692,695,697],{"class":277,"line":670},11,[275,672,673],{"class":289},".field",[275,675,676],{"class":281}," { ",[275,678,679],{"class":289},"display",[275,681,547],{"class":281},[275,683,550],{"class":289},[275,685,686],{"class":281},"; ",[275,688,689],{"class":289},"gap",[275,691,547],{"class":281},[275,693,694],{"class":289},"0.25",[275,696,567],{"class":566},[275,698,699],{"class":281},"; }\n",[275,701,703],{"class":277,"line":702},12,[275,704,667],{"emptyLinePlaceholder":666},[275,706,708,711],{"class":277,"line":707},13,[275,709,710],{"class":289},".field__input",[275,712,539],{"class":281},[275,714,716,718,720,722,724,727,729],{"class":277,"line":715},14,[275,717,586],{"class":289},[275,719,547],{"class":281},[275,721,169],{"class":289},[275,723,567],{"class":566},[275,725,726],{"class":289}," 0.6",[275,728,567],{"class":566},[275,730,553],{"class":281},[275,732,734,736,738,740,742,744,746],{"class":277,"line":733},15,[275,735,600],{"class":289},[275,737,547],{"class":281},[275,739,591],{"class":289},[275,741,607],{"class":566},[275,743,610],{"class":289},[275,745,613],{"class":289},[275,747,553],{"class":281},[275,749,751,753,755,757,759],{"class":277,"line":750},16,[275,752,620],{"class":289},[275,754,547],{"class":281},[275,756,195],{"class":289},[275,758,607],{"class":566},[275,760,553],{"class":281},[275,762,764,767,769,772],{"class":277,"line":763},17,[275,765,766],{"class":289},"  font",[275,768,547],{"class":281},[275,770,771],{"class":289},"inherit",[275,773,553],{"class":281},[275,775,777],{"class":277,"line":776},18,[275,778,661],{"class":281},[275,780,782],{"class":277,"line":781},19,[275,783,667],{"emptyLinePlaceholder":666},[275,785,787],{"class":277,"line":786},20,[275,788,790],{"class":789},"sLBg1","\u002F* The fieldset is never itself focusable, so :focus matching it is\n",[275,792,794],{"class":277,"line":793},21,[275,795,796],{"class":789},"   impossible — the group reacts entirely to its descendants. *\u002F\n",[275,798,800,803],{"class":277,"line":799},22,[275,801,802],{"class":289},".group:focus-within",[275,804,539],{"class":281},[275,806,808,811,813,816],{"class":277,"line":807},23,[275,809,810],{"class":289},"  border-color",[275,812,547],{"class":281},[275,814,815],{"class":289},"#2d5bff",[275,817,553],{"class":281},[275,819,821,824,826,828,831,833,836,838,841,844,847,850,853,856,858],{"class":277,"line":820},24,[275,822,823],{"class":289},"  box-shadow",[275,825,547],{"class":281},[275,827,579],{"class":289},[275,829,830],{"class":289}," 0",[275,832,830],{"class":289},[275,834,835],{"class":289}," 3",[275,837,607],{"class":566},[275,839,840],{"class":289}," rgb",[275,842,843],{"class":281},"(",[275,845,846],{"class":289},"45",[275,848,849],{"class":289}," 91",[275,851,852],{"class":289}," 255",[275,854,855],{"class":281}," \u002F ",[275,857,694],{"class":289},[275,859,860],{"class":281},");\n",[275,862,864],{"class":277,"line":863},25,[275,865,661],{"class":281},[275,867,869],{"class":277,"line":868},26,[275,870,667],{"emptyLinePlaceholder":666},[275,872,874],{"class":277,"line":873},27,[275,875,876],{"class":789},"\u002F* Reserve the help text's space at all times and animate only opacity,\n",[275,878,880],{"class":277,"line":879},28,[275,881,882],{"class":789},"   so revealing it cannot reflow the form while the user is tabbing. *\u002F\n",[275,884,886,889],{"class":277,"line":885},29,[275,887,888],{"class":289},".group__help",[275,890,539],{"class":281},[275,892,894,896,898,900],{"class":277,"line":893},30,[275,895,574],{"class":289},[275,897,547],{"class":281},[275,899,579],{"class":289},[275,901,553],{"class":281},[275,903,905,908,910,913,915],{"class":277,"line":904},31,[275,906,907],{"class":289},"  font-size",[275,909,547],{"class":281},[275,911,912],{"class":289},"0.8",[275,914,567],{"class":566},[275,916,553],{"class":281},[275,918,920,923,925,927],{"class":277,"line":919},32,[275,921,922],{"class":289},"  opacity",[275,924,547],{"class":281},[275,926,579],{"class":289},[275,928,553],{"class":281},[275,930,932,934,937,939,941,943],{"class":277,"line":931},33,[275,933,633],{"class":289},[275,935,936],{"class":281},": opacity ",[275,938,639],{"class":289},[275,940,642],{"class":566},[275,942,645],{"class":289},[275,944,553],{"class":281},[275,946,948],{"class":277,"line":947},34,[275,949,661],{"class":281},[275,951,953],{"class":277,"line":952},35,[275,954,667],{"emptyLinePlaceholder":666},[275,956,958,960,963,965,968,970,972],{"class":277,"line":957},36,[275,959,802],{"class":289},[275,961,962],{"class":289}," .group__help",[275,964,676],{"class":281},[275,966,967],{"class":289},"opacity",[275,969,547],{"class":281},[275,971,591],{"class":289},[275,973,699],{"class":281},[275,975,977],{"class":277,"line":976},37,[275,978,667],{"emptyLinePlaceholder":666},[275,980,982],{"class":277,"line":981},38,[275,983,984],{"class":789},"\u002F* The group ring is decorative context; the input keeps its own real\n",[275,986,988],{"class":277,"line":987},39,[275,989,990],{"class":789},"   indicator so the focused field is still individually identifiable. *\u002F\n",[275,992,994,997],{"class":277,"line":993},40,[275,995,996],{"class":289},".field__input:focus-visible",[275,998,539],{"class":281},[275,1000,1002,1005,1007,1010,1012,1014,1017],{"class":277,"line":1001},41,[275,1003,1004],{"class":289},"  outline",[275,1006,547],{"class":281},[275,1008,1009],{"class":289},"2",[275,1011,607],{"class":566},[275,1013,610],{"class":289},[275,1015,1016],{"class":289}," #2d5bff",[275,1018,553],{"class":281},[275,1020,1022,1025,1027,1029,1031],{"class":277,"line":1021},42,[275,1023,1024],{"class":289},"  outline-offset",[275,1026,547],{"class":281},[275,1028,591],{"class":289},[275,1030,607],{"class":566},[275,1032,553],{"class":281},[275,1034,1036],{"class":277,"line":1035},43,[275,1037,661],{"class":281},[275,1039,1041],{"class":277,"line":1040},44,[275,1042,667],{"emptyLinePlaceholder":666},[275,1044,1046,1049],{"class":277,"line":1045},45,[275,1047,1048],{"class":566},"@media",[275,1050,1051],{"class":281}," (prefers-reduced-motion: reduce) {\n",[275,1053,1055,1058,1060,1062,1064,1067,1069,1072,1075],{"class":277,"line":1054},46,[275,1056,1057],{"class":289},"  .group",[275,1059,98],{"class":281},[275,1061,888],{"class":289},[275,1063,676],{"class":281},[275,1065,1066],{"class":289},"transition-duration",[275,1068,547],{"class":281},[275,1070,1071],{"class":289},"0.01",[275,1073,1074],{"class":566},"ms",[275,1076,699],{"class":281},[275,1078,1080],{"class":277,"line":1079},47,[275,1081,661],{"class":281},[17,1083,1084,1085,1088,1089,54],{},"The rule that is easy to get wrong is the last one. A group ring is not a focus indicator — it tells you which ",[121,1086,1087],{},"region"," is active, not which control has focus, and a form with four fields in one group would leave a keyboard user unable to tell where they are. Keep the per-field indicator, and treat the group treatment as additional context. The contrast and thickness requirements for the real indicator are covered in ",[45,1090,1092],{"href":1091},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fcreating-accessible-focus-indicators\u002F","Creating Accessible Focus Indicators",[83,1094,1096],{"id":1095},"pattern-2-the-floating-label","Pattern 2: the floating label",[17,1098,1099,1100,1102,1103,1106],{},"The floating label needs two conditions joined by OR: float while the field is focused, and stay floated afterwards if the user typed something. ",[21,1101,27],{}," on the wrapper supplies the first; ",[21,1104,1105],{},":placeholder-shown"," on the input supplies the second.",[262,1108],{"name":1109},"hf-focus-within-floating-label",[266,1111,1113],{"className":268,"code":1112,"language":270,"meta":271,"style":271},"\u003Clabel class=\"float\">\n  \u003Cinput class=\"float__input\" type=\"email\" placeholder=\" \">\n  \u003Cspan class=\"float__label\">Email address\u003C\u002Fspan>\n\u003C\u002Flabel>\n",[21,1114,1115,1130,1159,1179],{"__ignoreMap":271},[275,1116,1117,1119,1121,1123,1125,1128],{"class":277,"line":278},[275,1118,282],{"class":281},[275,1120,349],{"class":285},[275,1122,290],{"class":289},[275,1124,293],{"class":281},[275,1126,1127],{"class":296},"\"float\"",[275,1129,300],{"class":281},[275,1131,1132,1134,1136,1138,1140,1143,1145,1147,1150,1152,1154,1157],{"class":277,"line":303},[275,1133,306],{"class":281},[275,1135,384],{"class":285},[275,1137,290],{"class":289},[275,1139,293],{"class":281},[275,1141,1142],{"class":296},"\"float__input\"",[275,1144,394],{"class":289},[275,1146,293],{"class":281},[275,1148,1149],{"class":296},"\"email\"",[275,1151,410],{"class":289},[275,1153,293],{"class":281},[275,1155,1156],{"class":296},"\" \"",[275,1158,300],{"class":281},[275,1160,1161,1163,1165,1167,1169,1172,1175,1177],{"class":277,"line":321},[275,1162,306],{"class":281},[275,1164,275],{"class":285},[275,1166,290],{"class":289},[275,1168,293],{"class":281},[275,1170,1171],{"class":296},"\"float__label\"",[275,1173,1174],{"class":281},">Email address\u003C\u002F",[275,1176,275],{"class":285},[275,1178,300],{"class":281},[275,1180,1181,1183,1185],{"class":277,"line":344},[275,1182,521],{"class":281},[275,1184,349],{"class":285},[275,1186,300],{"class":281},[266,1188,1190],{"className":528,"code":1189,"language":530,"meta":271,"style":271},".float {\n  position: relative;\n  display: block;\n  max-width: 20rem;\n  padding-top: 0.55rem;   \u002F* room for the label to travel into *\u002F\n}\n\n.float__input {\n  width: 100%;\n  padding: 0.8rem 0.7rem 0.45rem;\n  border: 1px solid #cbd5e1;\n  border-radius: 8px;\n  font: 400 0.95rem\u002F1.2 system-ui, sans-serif;\n}\n\n.float__label {\n  position: absolute;\n  left: 0.75rem;\n  top: 1.25rem;\n  font: 400 0.95rem\u002F1 system-ui, sans-serif;\n  \u002F* Clicks pass through to the input underneath. *\u002F\n  pointer-events: none;\n  transition: translate 0.16s ease, font-size 0.16s ease, color 0.16s ease;\n}\n\n\u002F* Condition A: the wrapper contains focus.\n   Condition B: the placeholder is gone, i.e. the field has a value. *\u002F\n.float:focus-within .float__label,\n.float__input:not(:placeholder-shown) + .float__label {\n  translate: 0 -1.15rem;\n  font-size: 0.72rem;\n}\n\n.float:focus-within .float__label { color: #2d5bff; }\n\n@media (prefers-reduced-motion: reduce) {\n  .float__label { transition-duration: 0.01ms; }\n}\n",[21,1191,1192,1199,1211,1222,1236,1254,1258,1262,1269,1283,1305,1321,1333,1362,1366,1370,1377,1388,1402,1416,1440,1445,1456,1493,1497,1501,1506,1511,1522,1543,1559,1572,1576,1580,1596,1600,1606,1623],{"__ignoreMap":271},[275,1193,1194,1197],{"class":277,"line":278},[275,1195,1196],{"class":289},".float",[275,1198,539],{"class":281},[275,1200,1201,1204,1206,1209],{"class":277,"line":303},[275,1202,1203],{"class":289},"  position",[275,1205,547],{"class":281},[275,1207,1208],{"class":289},"relative",[275,1210,553],{"class":281},[275,1212,1213,1215,1217,1220],{"class":277,"line":321},[275,1214,544],{"class":289},[275,1216,547],{"class":281},[275,1218,1219],{"class":289},"block",[275,1221,553],{"class":281},[275,1223,1224,1227,1229,1232,1234],{"class":277,"line":344},[275,1225,1226],{"class":289},"  max-width",[275,1228,547],{"class":281},[275,1230,1231],{"class":289},"20",[275,1233,567],{"class":566},[275,1235,553],{"class":281},[275,1237,1238,1241,1243,1246,1248,1251],{"class":277,"line":378},[275,1239,1240],{"class":289},"  padding-top",[275,1242,547],{"class":281},[275,1244,1245],{"class":289},"0.55",[275,1247,567],{"class":566},[275,1249,1250],{"class":281},";   ",[275,1252,1253],{"class":789},"\u002F* room for the label to travel into *\u002F\n",[275,1255,1256],{"class":277,"line":425},[275,1257,661],{"class":281},[275,1259,1260],{"class":277,"line":455},[275,1261,667],{"emptyLinePlaceholder":666},[275,1263,1264,1267],{"class":277,"line":487},[275,1265,1266],{"class":289},".float__input",[275,1268,539],{"class":281},[275,1270,1271,1274,1276,1278,1281],{"class":277,"line":508},[275,1272,1273],{"class":289},"  width",[275,1275,547],{"class":281},[275,1277,191],{"class":289},[275,1279,1280],{"class":566},"%",[275,1282,553],{"class":281},[275,1284,1285,1287,1289,1291,1293,1296,1298,1301,1303],{"class":277,"line":518},[275,1286,586],{"class":289},[275,1288,547],{"class":281},[275,1290,912],{"class":289},[275,1292,567],{"class":566},[275,1294,1295],{"class":289}," 0.7",[275,1297,567],{"class":566},[275,1299,1300],{"class":289}," 0.45",[275,1302,567],{"class":566},[275,1304,553],{"class":281},[275,1306,1307,1309,1311,1313,1315,1317,1319],{"class":277,"line":670},[275,1308,600],{"class":289},[275,1310,547],{"class":281},[275,1312,591],{"class":289},[275,1314,607],{"class":566},[275,1316,610],{"class":289},[275,1318,613],{"class":289},[275,1320,553],{"class":281},[275,1322,1323,1325,1327,1329,1331],{"class":277,"line":702},[275,1324,620],{"class":289},[275,1326,547],{"class":281},[275,1328,181],{"class":289},[275,1330,607],{"class":566},[275,1332,553],{"class":281},[275,1334,1335,1337,1339,1342,1345,1347,1349,1352,1355,1357,1360],{"class":277,"line":707},[275,1336,766],{"class":289},[275,1338,547],{"class":281},[275,1340,1341],{"class":289},"400",[275,1343,1344],{"class":289}," 0.95",[275,1346,567],{"class":566},[275,1348,32],{"class":281},[275,1350,1351],{"class":289},"1.2",[275,1353,1354],{"class":289}," system-ui",[275,1356,98],{"class":281},[275,1358,1359],{"class":289},"sans-serif",[275,1361,553],{"class":281},[275,1363,1364],{"class":277,"line":715},[275,1365,661],{"class":281},[275,1367,1368],{"class":277,"line":733},[275,1369,667],{"emptyLinePlaceholder":666},[275,1371,1372,1375],{"class":277,"line":750},[275,1373,1374],{"class":289},".float__label",[275,1376,539],{"class":281},[275,1378,1379,1381,1383,1386],{"class":277,"line":763},[275,1380,1203],{"class":289},[275,1382,547],{"class":281},[275,1384,1385],{"class":289},"absolute",[275,1387,553],{"class":281},[275,1389,1390,1393,1395,1398,1400],{"class":277,"line":776},[275,1391,1392],{"class":289},"  left",[275,1394,547],{"class":281},[275,1396,1397],{"class":289},"0.75",[275,1399,567],{"class":566},[275,1401,553],{"class":281},[275,1403,1404,1407,1409,1412,1414],{"class":277,"line":781},[275,1405,1406],{"class":289},"  top",[275,1408,547],{"class":281},[275,1410,1411],{"class":289},"1.25",[275,1413,567],{"class":566},[275,1415,553],{"class":281},[275,1417,1418,1420,1422,1424,1426,1428,1430,1432,1434,1436,1438],{"class":277,"line":786},[275,1419,766],{"class":289},[275,1421,547],{"class":281},[275,1423,1341],{"class":289},[275,1425,1344],{"class":289},[275,1427,567],{"class":566},[275,1429,32],{"class":281},[275,1431,591],{"class":289},[275,1433,1354],{"class":289},[275,1435,98],{"class":281},[275,1437,1359],{"class":289},[275,1439,553],{"class":281},[275,1441,1442],{"class":277,"line":793},[275,1443,1444],{"class":789},"  \u002F* Clicks pass through to the input underneath. *\u002F\n",[275,1446,1447,1450,1452,1454],{"class":277,"line":799},[275,1448,1449],{"class":289},"  pointer-events",[275,1451,547],{"class":281},[275,1453,166],{"class":289},[275,1455,553],{"class":281},[275,1457,1458,1460,1463,1466,1468,1470,1473,1475,1477,1479,1481,1484,1487,1489,1491],{"class":277,"line":807},[275,1459,633],{"class":289},[275,1461,1462],{"class":281},": translate ",[275,1464,1465],{"class":289},"0.16",[275,1467,642],{"class":566},[275,1469,645],{"class":289},[275,1471,1472],{"class":281},", font-size ",[275,1474,1465],{"class":289},[275,1476,642],{"class":566},[275,1478,645],{"class":289},[275,1480,98],{"class":281},[275,1482,1483],{"class":289},"color",[275,1485,1486],{"class":289}," 0.16",[275,1488,642],{"class":566},[275,1490,645],{"class":289},[275,1492,553],{"class":281},[275,1494,1495],{"class":277,"line":820},[275,1496,661],{"class":281},[275,1498,1499],{"class":277,"line":863},[275,1500,667],{"emptyLinePlaceholder":666},[275,1502,1503],{"class":277,"line":868},[275,1504,1505],{"class":789},"\u002F* Condition A: the wrapper contains focus.\n",[275,1507,1508],{"class":277,"line":873},[275,1509,1510],{"class":789},"   Condition B: the placeholder is gone, i.e. the field has a value. *\u002F\n",[275,1512,1513,1516,1519],{"class":277,"line":879},[275,1514,1515],{"class":289},".float:focus-within",[275,1517,1518],{"class":289}," .float__label",[275,1520,1521],{"class":281},",\n",[275,1523,1524,1527,1530,1533,1536,1539,1541],{"class":277,"line":885},[275,1525,1526],{"class":289},".float__input:not",[275,1528,1529],{"class":281},"(:",[275,1531,1532],{"class":285},"placeholder-shown",[275,1534,1535],{"class":281},") ",[275,1537,1538],{"class":566},"+",[275,1540,1518],{"class":289},[275,1542,539],{"class":281},[275,1544,1545,1548,1550,1552,1555,1557],{"class":277,"line":893},[275,1546,1547],{"class":289},"  translate",[275,1549,547],{"class":281},[275,1551,579],{"class":289},[275,1553,1554],{"class":289}," -1.15",[275,1556,567],{"class":566},[275,1558,553],{"class":281},[275,1560,1561,1563,1565,1568,1570],{"class":277,"line":904},[275,1562,907],{"class":289},[275,1564,547],{"class":281},[275,1566,1567],{"class":289},"0.72",[275,1569,567],{"class":566},[275,1571,553],{"class":281},[275,1573,1574],{"class":277,"line":919},[275,1575,661],{"class":281},[275,1577,1578],{"class":277,"line":931},[275,1579,667],{"emptyLinePlaceholder":666},[275,1581,1582,1584,1586,1588,1590,1592,1594],{"class":277,"line":947},[275,1583,1515],{"class":289},[275,1585,1518],{"class":289},[275,1587,676],{"class":281},[275,1589,1483],{"class":289},[275,1591,547],{"class":281},[275,1593,815],{"class":289},[275,1595,699],{"class":281},[275,1597,1598],{"class":277,"line":952},[275,1599,667],{"emptyLinePlaceholder":666},[275,1601,1602,1604],{"class":277,"line":957},[275,1603,1048],{"class":566},[275,1605,1051],{"class":281},[275,1607,1608,1611,1613,1615,1617,1619,1621],{"class":277,"line":976},[275,1609,1610],{"class":289},"  .float__label",[275,1612,676],{"class":281},[275,1614,1066],{"class":289},[275,1616,547],{"class":281},[275,1618,1071],{"class":289},[275,1620,1074],{"class":566},[275,1622,699],{"class":281},[275,1624,1625],{"class":277,"line":981},[275,1626,661],{"class":281},[17,1628,1629,1630,1633,1634,1636,1637,1640],{},"The ",[21,1631,1632],{},"placeholder=\" \""," — a single space, not an empty attribute — is not decoration. ",[21,1635,1105],{}," matches only while a placeholder is actually being displayed, and an input with no ",[21,1638,1639],{},"placeholder"," attribute never matches it, so the second condition would never fire. A space is a placeholder that renders as nothing, which gives you the state without any visible text competing with the label.",[83,1642,1644],{"id":1643},"the-technique-that-makes-it-work","The technique that makes it work",[17,1646,1647,1648,1650,1651,1653],{},"Both patterns rely on the same structural move: put the state on a wrapper, and let the wrapper's state drive its descendants and its own box. CSS still has no way to select an earlier sibling or a parent from the focused element, and ",[21,1649,245],{}," is only recently universal. ",[21,1652,27],{}," sidesteps the problem entirely — instead of asking \"which element is focused, and how do I reach its label from there\", you ask \"does this container hold focus\", and every descendant is then addressable with an ordinary descendant selector.",[17,1655,1656,1657,1660,1661,1664,1665,1667,1668,1670,1671,1673],{},"That inversion is why the floating label works with a ",[21,1658,1659],{},"\u003Cspan>"," ",[121,1662,1663],{},"after"," the input in source order. You are not selecting backwards; you are selecting forwards from a wrapper that happens to know about the focus. The ",[21,1666,1538],{}," combinator in the ",[21,1669,1105],{}," half is a plain next-sibling selection, and the ",[21,1672,27],{}," half does not need a combinator at all.",[83,1675,1677],{"id":1676},"variation-keyboard-only-group-highlighting","Variation: keyboard-only group highlighting",[17,1679,1680,1682,1683,1685,1686,1696,1697,1700,1701,1703],{},[21,1681,27],{}," fires for pointer focus too, so clicking into a field lights up its group — which some designers dislike for the same reason they disliked the old ",[21,1684,97],{}," ring, a debate covered in ",[45,1687,1689,1692,1693],{"href":1688},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Ffocus-visible-vs-focus-polyfill-alternatives\u002F",[1690,1691],"focus-visible",{}," vs ",[1694,1695],"focus",{},". There is no ",[21,1698,1699],{},":focus-visible-within"," pseudo-class, but ",[21,1702,245],{}," composes to the same thing:",[266,1705,1707],{"className":528,"code":1706,"language":530,"meta":271,"style":271},"\u002F* Baseline: every engine gets the group ring. *\u002F\n.group:focus-within {\n  border-color: #2d5bff;\n}\n\n\u002F* Where :has() is available, restrict the ring to keyboard-driven focus\n   by asking whether a descendant matches the focus-visible heuristic. *\u002F\n@supports selector(:has(*)) {\n  .group:focus-within { border-color: #cbd5e1; }\n  .group:has(:focus-visible) { border-color: #2d5bff; }\n}\n",[21,1708,1709,1714,1720,1730,1734,1738,1743,1748,1759,1776,1797],{"__ignoreMap":271},[275,1710,1711],{"class":277,"line":278},[275,1712,1713],{"class":789},"\u002F* Baseline: every engine gets the group ring. *\u002F\n",[275,1715,1716,1718],{"class":277,"line":303},[275,1717,802],{"class":289},[275,1719,539],{"class":281},[275,1721,1722,1724,1726,1728],{"class":277,"line":321},[275,1723,810],{"class":289},[275,1725,547],{"class":281},[275,1727,815],{"class":289},[275,1729,553],{"class":281},[275,1731,1732],{"class":277,"line":344},[275,1733,661],{"class":281},[275,1735,1736],{"class":277,"line":378},[275,1737,667],{"emptyLinePlaceholder":666},[275,1739,1740],{"class":277,"line":425},[275,1741,1742],{"class":789},"\u002F* Where :has() is available, restrict the ring to keyboard-driven focus\n",[275,1744,1745],{"class":277,"line":455},[275,1746,1747],{"class":789},"   by asking whether a descendant matches the focus-visible heuristic. *\u002F\n",[275,1749,1750,1753,1756],{"class":277,"line":487},[275,1751,1752],{"class":566},"@supports",[275,1754,1755],{"class":289}," selector",[275,1757,1758],{"class":281},"(:has(*)) {\n",[275,1760,1761,1764,1766,1769,1771,1774],{"class":277,"line":508},[275,1762,1763],{"class":289},"  .group:focus-within",[275,1765,676],{"class":281},[275,1767,1768],{"class":289},"border-color",[275,1770,547],{"class":281},[275,1772,1773],{"class":289},"#cbd5e1",[275,1775,699],{"class":281},[275,1777,1778,1781,1783,1786,1789,1791,1793,1795],{"class":277,"line":518},[275,1779,1780],{"class":289},"  .group:has",[275,1782,843],{"class":281},[275,1784,1785],{"class":289},":focus-visible",[275,1787,1788],{"class":281},") { ",[275,1790,1768],{"class":289},[275,1792,547],{"class":281},[275,1794,815],{"class":289},[275,1796,699],{"class":281},[275,1798,1799],{"class":277,"line":670},[275,1800,661],{"class":281},[17,1802,1629,1803,1806,1807,1809],{},[21,1804,1805],{},"@supports selector()"," guard is what keeps this safe: without it, an engine lacking ",[21,1808,245],{}," would discard the second rule as invalid but still apply the first override, leaving the group with no highlight at all.",[83,1811,1813],{"id":1812},"browser-support","Browser support",[17,1815,1816,1818,1819,1821,1822,1824,1825,1827,1828,1831,1832,1835],{},[21,1817,27],{}," is old and universally available across every current engine, so no fallback is needed. ",[21,1820,1105],{}," is comparably broad and equally safe to use unguarded. ",[21,1823,245],{}," is the newest piece, at Chrome and Edge 105+, Safari 15.4+ and Firefox 121+, which is why the keyboard-only variation is wrapped in an ",[21,1826,1805],{}," block rather than used as the baseline. The standalone ",[21,1829,1830],{},"translate"," property used for the label needs Chrome and Edge 104+, Firefox 72+ or Safari 14.1+; ",[21,1833,1834],{},"transform: translate()"," substitutes exactly if you need older engines.",[83,1837,1839],{"id":1838},"faq","FAQ",[17,1841,1842,1850,1851,1853,1854,1856,1857,1859,1860,1862],{},[58,1843,1844,1845,79,1847,1849],{},"What is the difference between ",[21,1846,27],{},[21,1848,42],{},"?","\nBoth match an ancestor of a focused element, but ",[21,1852,27],{}," also matches when the element itself is focused, whereas ",[21,1855,42],{}," only matches when a descendant is focused. They also differ in weight: ",[21,1858,27],{}," counts as one pseudo-class, while ",[21,1861,245],{}," takes the specificity of its most specific argument.",[17,1864,1865,1871,1872,79,1874,1876],{},[58,1866,1867,1868,1870],{},"Can ",[21,1869,27],{}," replace JavaScript focus and blur listeners?","\nFor styling, almost always. A ",[21,1873,31],{},[21,1875,35],{}," listener toggling a class does the same job with more code and a chance of the class going stale. Keep script only where focus must change behaviour rather than appearance, such as opening a dialog.",[17,1878,1879,1882,1883,1885,1886,98,1889,1892,1893,1895,1896,1898],{},[58,1880,1881],{},"Why does my layout jump when a field gains focus?","\nBecause the rule inside ",[21,1884,27],{}," changes a property that affects layout, such as ",[21,1887,1888],{},"height",[21,1890,1891],{},"padding"," or ",[21,1894,679],{},". Reserve the space up front and animate ",[21,1897,967],{}," or a transform instead, so tabbing through the form never reflows the page.",[17,1900,1901,1906,1907,1700,1909,1912,1913,1915,1916,1918],{},[58,1902,1903,1904,1849],{},"Is there a keyboard-only version of ",[21,1905,27],{},"\nThere is no ",[21,1908,1699],{},[21,1910,1911],{},":has(:focus-visible)"," produces the same effect, matching a container only when a descendant shows the focus-visible heuristic. It needs ",[21,1914,245],{}," support, so keep ",[21,1917,27],{}," as the baseline.",[83,1920,1922],{"id":1921},"related","Related",[62,1924,1925,1930,1939,1944,1949],{},[65,1926,1927,1929],{},[45,1928,48],{"href":47}," — the parent guide on interactive state styling.",[65,1931,1932,1938],{},[45,1933,1934,1692,1936],{"href":1688},[1690,1935],{},[1694,1937],{}," — the modality heuristic these patterns can borrow.",[65,1940,1941,1943],{},[45,1942,53],{"href":52}," — pairing focus reveals with hover reveals safely.",[65,1945,1946,1948],{},[45,1947,1092],{"href":1091}," — the per-field ring a group highlight must never replace.",[65,1950,1951,1955],{},[45,1952,1954],{"href":1953},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fresponsive-forms-with-container-queries\u002F","Responsive Forms With Container Queries"," — laying these field groups out against available width.",[1957,1958,1959],"style",{},"html pre.shiki code .suds8, html code.shiki .suds8{--shiki-default:#0E1116;--shiki-dark:#F0F3F6}html pre.shiki code .sne4z, html code.shiki .sne4z{--shiki-default:#024C1A;--shiki-dark:#72F088}html pre.shiki code .s-5SL, html code.shiki .s-5SL{--shiki-default:#023B95;--shiki-dark:#91CBFF}html pre.shiki code .sT6z2, html code.shiki .sT6z2{--shiki-default:#032563;--shiki-dark:#ADDCFF}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 .sHUrx, html code.shiki .sHUrx{--shiki-default:#A0111F;--shiki-dark:#FF9492}html pre.shiki code .sLBg1, html code.shiki .sLBg1{--shiki-default:#66707B;--shiki-dark:#BDC4CC}",{"title":271,"searchDepth":303,"depth":303,"links":1961},[1962,1964,1965,1966,1967,1968,1969,1970],{"id":85,"depth":303,"text":1963},"What :focus-within actually matches",{"id":256,"depth":303,"text":257},{"id":1095,"depth":303,"text":1096},{"id":1643,"depth":303,"text":1644},{"id":1676,"depth":303,"text":1677},{"id":1812,"depth":303,"text":1813},{"id":1838,"depth":303,"text":1839},{"id":1921,"depth":303,"text":1922},"Use :focus-within to highlight active field groups, float labels and reveal help text, and learn exactly how it differs from :has(:focus) in match and weight.","md",{"pageTitle":1974,"datePublished":1975,"dateModified":1975,"faq":1976},":focus-within Patterns for Form UX","2026-07-20",[1977,1980,1983,1985],{"q":1978,"a":1979},"What is the difference between :focus-within and :has(:focus)?","Both match an ancestor of a focused element, but :focus-within also matches when the element itself is focused, whereas :has(:focus) only matches when a descendant is focused. They also differ in weight: :focus-within counts as one pseudo-class, while :has() takes the specificity of its most specific argument.",{"q":1981,"a":1982},"Can :focus-within replace JavaScript focus and blur listeners?","For styling, almost always. A focusin and focusout listener toggling a class does the same job with more code and a chance of the class going stale. Keep script only where focus must change behaviour rather than appearance, such as opening a dialog.",{"q":1881,"a":1984},"Because the rule inside :focus-within changes a property that affects layout, such as height, padding or display. Reserve the space up front and animate opacity or a transform instead, so tabbing through the form never reflows the page.",{"q":1986,"a":1987},"Is there a keyboard-only version of :focus-within?","There is no :focus-visible-within pseudo-class, but :has(:focus-visible) produces the same effect, matching a container only when a descendant shows the focus-visible heuristic. It needs :has() support, so keep :focus-within as the baseline.","\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Ffocus-within-form-patterns",{"title":5,"description":1971},"css-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Ffocus-within-form-patterns\u002Findex","vAcL4I6Fuu10uh6GiO8ucNKFDmiHABoqd9TX2f3lz0A",[1993,1996,1999,2002,2005,2008,2011,2014,2017,2020,2023,2026,2029,2032,2035,2038,2041,2044,2047,2050,2053,2056,2059,2062,2063,2066,2069,2072,2075,2078,2081,2084,2087,2090,2093,2096,2099,2102,2105,2108,2111,2114,2117,2120,2123,2126,2129,2131,2134,2137,2140,2143,2146,2149,2152,2155,2158,2161,2164,2167,2170,2173,2176,2179,2182,2185,2188,2191,2194,2197,2200,2203,2206,2209,2212,2215,2218,2221,2224,2227,2230,2233,2236,2239,2242,2245,2248,2251],{"path":1994,"title":1995},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fcreating-accessible-focus-indicators","Creating Accessible Focus Indicators: Building a Ring That Survives Any Background",{"path":1997,"title":1998},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations","Accessibility in CSS Animations: Patterns, Specs & Best Practices",{"path":2000,"title":2001},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fprefers-reduced-motion-recipes","prefers-reduced-motion Recipes: A Reduction for Every Common Effect",{"path":2003,"title":2004},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Freducing-motion-preferences-in-css","Reducing Motion Preferences in CSS: How the Setting Becomes a Media Feature",{"path":2006,"title":2007},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Ftarget-size-and-pointer-accessibility","Target Size and Pointer Accessibility: Meeting 2.5.8 Without Redesigning",{"path":2009,"title":2010},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fvestibular-safe-animation-patterns","Vestibular-Safe Animation Patterns: Why Some Motion Makes People Sick",{"path":2012,"title":2013},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fwcag-motion-success-criteria","WCAG Motion Success Criteria: What Each One Actually Requires",{"path":2015,"title":2016},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion\u002Fcontainer-query-hover-affordances","Container Query Hover Affordances: When Narrow Means Persistent, Not Hidden",{"path":2018,"title":2019},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion","Container-Aware Motion: Animation Sized by the Space a Component Actually Has",{"path":2021,"title":2022},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion\u002Fmotion-that-scales-with-container-size","Motion That Scales with Container Size: cqi, cqb, and Derived Durations",{"path":2024,"title":2025},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion\u002Fsuppressing-motion-in-small-containers","Suppressing Motion in Small Containers: Deliberately Animating Nothing",{"path":2027,"title":2028},"\u002Fcss-only-micro-interactions-animations\u002Fcss-animation-vs-web-animations-api","CSS Animation vs the Web Animations API: A Decision Guide",{"path":2030,"title":2031},"\u002Fcss-only-micro-interactions-animations\u002Fcss-animation-vs-web-animations-api\u002Fwhen-to-use-javascript-animation","When to Use JavaScript Instead of CSS for Animation",{"path":2033,"title":2034},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture\u002Fanimating-custom-properties-with-at-property","Animating Custom Properties with @property: Typed Variables That Interpolate",{"path":2036,"title":2037},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture\u002Ffluid-spacing-tokens-driving-transition-durations","Fluid Spacing Tokens That Drive Transition Durations",{"path":2039,"title":2040},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture","CSS Custom Properties Architecture: Scalable Design Systems & Dynamic UI",{"path":2042,"title":2043},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture\u002Fregistered-properties-and-type-safety","Registered Properties and Type Safety: @property as a Contract for Design Tokens",{"path":2045,"title":2046},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Fcss-transition-timing-functions","CSS Transition Timing Functions: ease, cubic-bezier(), steps() and linear()",{"path":2048,"title":2049},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals","CSS Transition Fundamentals: Architecture, Performance & Patterns",{"path":2051,"title":2052},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Fstarting-style-entry-animations","@starting-style: Animating an Element's Very First Style Change",{"path":2054,"title":2055},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Ftransitioning-display-with-allow-discrete","Transitioning display with transition-behavior: allow-discrete and @starting-style",{"path":2057,"title":2058},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Faccessible-css-only-tooltips","Accessible CSS-Only Tooltips and Hover Cards With  and ",{"path":2060,"title":2061},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Ffocus-visible-vs-focus-polyfill-alternatives"," vs :focus: Why It Replaced the Focus-Ring Polyfill",{"path":1988,"title":5},{"path":2064,"title":2065},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design","Hover & Focus State Design: Spec-Compliant Patterns for Modern UIs",{"path":2067,"title":2068},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Fpointer-and-hover-media-queries","Pointer and Hover Media Queries: Gating Affordances by Input Capability",{"path":2070,"title":2071},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Fsmooth-hover-effects-without-javascript","Smooth Hover Effects Without JavaScript: CSS-Only Patterns for Modern UI",{"path":2073,"title":2074},"\u002Fcss-only-micro-interactions-animations","CSS-Only Micro-Interactions & Animations: Architecture, Performance & Implementation",{"path":2076,"title":2077},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcontainer-query-triggered-keyframe-animations","Container-Query-Triggered Keyframe Animations: Animate Differently per Available Space",{"path":2079,"title":2080},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcss-only-accordions-and-disclosure","CSS-Only Accordions and Disclosure Widgets with details and summary",{"path":2082,"title":2083},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcss-only-loading-spinners-and-skeletons","CSS-Only Loading Spinners and Skeleton Shimmer Screens with @keyframes",{"path":2085,"title":2086},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcss-only-toggle-switches-and-checkboxes","CSS-Only Toggle Switches and Checkboxes That Keep the Native Input",{"path":2088,"title":2089},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns","Keyframe Animation Patterns: Spec-Compliant Architectures for Modern UIs",{"path":2091,"title":2092},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fstaggered-list-animations-with-custom-properties","Staggered List Animations Using --i Index Custom Properties",{"path":2094,"title":2095},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration","Performance & GPU Acceleration in CSS: A Developer’s Blueprint",{"path":2097,"title":2098},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration\u002Foptimizing-css-animations-for-60fps","Optimizing CSS Animations for 60fps: Budgeting the Frame",{"path":2100,"title":2101},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration\u002Fprofiling-animations-in-devtools","Profiling CSS Animations in DevTools: Finding the Frame That Costs Too Much",{"path":2103,"title":2104},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration\u002Fwill-change-and-the-compositor-thread","will-change and the Compositor Thread: Layer Promotion Without the Footguns",{"path":2106,"title":2107},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations","Scroll-Driven Animations: Binding Motion to Scroll Position in Pure CSS",{"path":2109,"title":2110},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-driven-animation-fallbacks","Shipping Scroll-Driven Animations Without Breaking Anything",{"path":2112,"title":2113},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-progress-bar-without-javascript","A Reading Progress Bar Driven by scroll(root)",{"path":2115,"title":2116},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-triggered-reveal-animations","Reveal-on-Scroll with view() and animation-range",{"path":2118,"title":2119},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers","View Transitions for CSS Developers: the Pseudo-Element Tree, Snapshots, and Named Elements",{"path":2121,"title":2122},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers\u002Fsame-document-view-transitions","Writing a Same-Document View Transition: the CSS You Actually Author",{"path":2124,"title":2125},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers\u002Fview-transition-names-shared-elements","view-transition-name and Shared Elements: Morphing a Thumbnail into a Hero",{"path":2127,"title":2128},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers\u002Fview-transitions-with-reduced-motion","Honouring prefers-reduced-motion in View Transitions",{"path":32,"title":2130},"Modern CSS Layouts & Micro-Interactions",{"path":2132,"title":2133},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fanchor-positioned-tooltips","Anchor-Positioned Tooltips: Tethering, Flipping, and Getting the Semantics Right",{"path":2135,"title":2136},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fanchor-positioning-fallbacks","Anchor Positioning Fallbacks: A Graceful Floor for Older Browser Versions",{"path":2138,"title":2139},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays","CSS Anchor Positioning and Overlays: Tethering Elements Without JavaScript",{"path":2141,"title":2142},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fpopover-attribute-and-css-styling","The popover Attribute: Top-Layer Overlays With No JavaScript",{"path":2144,"title":2145},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks\u002Ffeature-detection-with-supports","Feature Detection with @supports: Syntax, Semantics, and the Traps",{"path":2147,"title":2148},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks\u002Fhandling-container-query-fallbacks-for-older-browsers","Handling Container Query Fallbacks for Older Browsers",{"path":2150,"title":2151},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks","Container Query Fallbacks: Spec-Compliant CSS Strategies for Legacy Browsers",{"path":2153,"title":2154},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fcontainer-query-units-cqi-cqb-explained","Container Query Units Explained: cqi, cqb, cqw, cqh, cqmin and cqmax",{"path":2156,"title":2157},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fcontainer-type-size-vs-inline-size","Choosing container-type: inline-size, size, or normal",{"path":2159,"title":2160},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fcontainer-vs-media-queries-comparison","Container vs Media Queries: Choosing the Right Reference Box",{"path":2162,"title":2163},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fhow-to-use-container-queries-in-production","How to Use Container Queries in Production Without a Rewrite",{"path":2165,"title":2166},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics","Container Query Syntax Basics",{"path":2168,"title":2169},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fnesting-and-naming-container-queries","Nesting and Naming Container Queries: Targeting the Right Ancestor",{"path":2171,"title":2172},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fauto-fit-minmax-responsive-grids","Responsive Grids with repeat(auto-fit, minmax())",{"path":2174,"title":2175},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fbuilding-a-dashboard-with-subgrid","Building a Responsive Dashboard Where Cards Align with Subgrid",{"path":2177,"title":2178},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fholy-grail-layout-with-grid","The Holy-Grail Layout with CSS Grid and Container Queries",{"path":2180,"title":2181},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts","CSS Grid and Subgrid Layouts for Responsive Interfaces",{"path":2183,"title":2184},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fsubgrid-vs-nested-grid","Subgrid vs an Independently Nested Grid",{"path":2186,"title":2187},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Fclamp-vs-media-query-typography","clamp() Fluid Type vs Media-Query Step Typography: Tradeoffs and Anatomy",{"path":2189,"title":2190},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-space-scale-with-clamp","Building a Fluid Spacing Scale with clamp() and Container Units",{"path":2192,"title":2193},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-type-accessibility-and-zoom","Fluid Type, Accessibility and Zoom: The vw-Only Trap",{"path":2195,"title":2196},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-typography-without-javascript","Fluid Typography Without JavaScript: A Precision CSS Reference",{"path":2198,"title":2199},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp","Fluid Typography with clamp(): A Practical Guide for Modern CSS",{"path":2201,"title":2202},"\u002Fmastering-container-queries-responsive-layouts","Mastering Container Queries & Responsive Layouts",{"path":2204,"title":2205},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Faspect-ratio-for-responsive-media","Using aspect-ratio for Responsive Media and Preventing Layout Shift",{"path":2207,"title":2208},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques","Intrinsic Sizing Techniques: Modern CSS Layouts for Responsive UI",{"path":2210,"title":2211},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Fmin-max-fit-content-explained","min-content, max-content, and fit-content() Explained",{"path":2213,"title":2214},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Fpreventing-flex-and-grid-overflow","The min-width: auto Trap in Flex and Grid Layouts",{"path":2216,"title":2217},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies\u002Fcascade-layers-for-reset-and-tokens","Cascade Layers for Reset and Design Tokens",{"path":2219,"title":2220},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies\u002Fcascade-layers-vs-specificity-hacks","Cascade Layers vs Specificity Hacks: Ordering Instead of Escalating",{"path":2222,"title":2223},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies","Modern CSS Reset Strategies: A Spec-Compliant Foundation",{"path":2225,"title":2226},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fbuilding-responsive-cards-with-container-queries","Building Responsive Cards with Container Queries: A Production-Ready Guide",{"path":2228,"title":2229},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fcontainer-query-data-tables","Container Query Data Tables: Reflowing Tables to Cards",{"path":2231,"title":2232},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fcontainer-query-sidebar-layouts","Container Query Sidebar Layouts That Travel Between Contexts",{"path":2234,"title":2235},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns","Responsive Component Patterns: Architecture & Implementation",{"path":2237,"title":2238},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fresponsive-forms-with-container-queries","Responsive Forms with Container Queries: Reflow Without Losing the Wiring",{"path":2240,"title":2241},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fresponsive-navigation-without-media-queries","Responsive Navigation Without Media Queries Using Container Queries",{"path":2243,"title":2244},"\u002Fmastering-container-queries-responsive-layouts\u002Fstyle-queries-and-container-state\u002Fcontainer-style-query-theming","Container Style Query Theming: One Token Instead of a Variant Class Matrix",{"path":2246,"title":2247},"\u002Fmastering-container-queries-responsive-layouts\u002Fstyle-queries-and-container-state","Style Queries and Container State: Token-Driven Component Variants",{"path":2249,"title":2250},"\u002Fmastering-container-queries-responsive-layouts\u002Fstyle-queries-and-container-state\u002Fstyle-queries-with-custom-properties","Style Queries With Custom Properties: Declaring, Inheriting, and Matching Tokens",{"path":2252,"title":2253},"\u002Fmastering-container-queries-responsive-layouts\u002Fstyle-queries-and-container-state\u002Fstyle-query-fallbacks-and-support","Style Query Fallbacks and Support: Designing for Older Browser Versions",1784566157864]