[{"data":1,"prerenderedAt":1379},["ShallowReactive",2],{"blog-post:using-slots-in-vue":3},{"id":4,"title":5,"body":6,"date":1368,"description":1369,"draft":1370,"extension":1371,"lastModified":1372,"meta":1373,"navigation":141,"path":1374,"readingTime":1375,"seo":1376,"stem":1377,"__hash__":1378},"posts\u002Fblog\u002Fusing-slots-in-vue.md","Slots and Scoped Slots in Vue.js",{"type":7,"value":8,"toc":1364},"minimark",[9,13,18,26,214,272,282,292,431,493,503,511,531,545,633,691,699,702,809,813,827,1021,1144,1147,1325,1346,1349,1360],[10,11,12],"p",{},"Slots allows us to reuse and control presentation of a UI component in a simple\nway. Most common use case of slots is delegating \"what\" to render to the parent\ncomponent (or the component using the slot) and adding to that, scoped-slots\nallow us to pass back data to the parent component to help it render the \"what\".",[14,15,17],"h2",{"id":16},"why-would-i-need-slots","Why would I need slots?",[10,19,20,21,25],{},"Let's say we have a Custom Submit button used in our form and it has two states\nbased on the form. An initial state where Button is active with 'Submit' text\nand a Loading state where Button is disabled and showing 'Loading...'' text.\nTypically, change of button state based on the form is managed in the component\nthat holds the form state. So it makes sense to just pass props to our Custom\nSubmit button whether it should show active or disabled states. This is how our\n",[22,23,24],"code",{},"CustomSubmitButton"," would look like:",[27,28,33],"pre",{"className":29,"code":30,"language":31,"meta":32,"style":32},"language-vue shiki shiki-themes github-dark","\u002F* CustomSubmitButton.vue *\u002F\n\u003Cscript setup lang=\"ts\">\nwithDefaults(defineProps\u003C{ loading: boolean }>(), {\n  loading: false,\n});\n\u002F* Other CustomSubmitButton related Logic, Analytic events etc here.. *\u002F\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"wrapper\">\n    \u003Cbutton class=\"submit-btn\">{{ loading ? \"Loading...\" : \"Submit\" }}\u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","vue","",[22,34,35,44,71,101,113,119,126,136,143,153,172,195,205],{"__ignoreMap":32},[36,37,40],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"s95oV","\u002F* CustomSubmitButton.vue *\u002F\n",[36,45,47,50,54,58,61,64,68],{"class":38,"line":46},2,[36,48,49],{"class":42},"\u003C",[36,51,53],{"class":52},"s4JwU","script",[36,55,57],{"class":56},"svObZ"," setup",[36,59,60],{"class":56}," lang",[36,62,63],{"class":42},"=",[36,65,67],{"class":66},"sU2Wk","\"ts\"",[36,69,70],{"class":42},">\n",[36,72,74,77,80,83,86,90,94,98],{"class":38,"line":73},3,[36,75,76],{"class":56},"withDefaults",[36,78,79],{"class":42},"(",[36,81,82],{"class":56},"defineProps",[36,84,85],{"class":42},"\u003C{ ",[36,87,89],{"class":88},"s9osk","loading",[36,91,93],{"class":92},"snl16",":",[36,95,97],{"class":96},"sDLfK"," boolean",[36,99,100],{"class":42}," }>(), {\n",[36,102,104,107,110],{"class":38,"line":103},4,[36,105,106],{"class":42},"  loading: ",[36,108,109],{"class":96},"false",[36,111,112],{"class":42},",\n",[36,114,116],{"class":38,"line":115},5,[36,117,118],{"class":42},"});\n",[36,120,122],{"class":38,"line":121},6,[36,123,125],{"class":124},"sAwPA","\u002F* Other CustomSubmitButton related Logic, Analytic events etc here.. *\u002F\n",[36,127,129,132,134],{"class":38,"line":128},7,[36,130,131],{"class":42},"\u003C\u002F",[36,133,53],{"class":52},[36,135,70],{"class":42},[36,137,139],{"class":38,"line":138},8,[36,140,142],{"emptyLinePlaceholder":141},true,"\n",[36,144,146,148,151],{"class":38,"line":145},9,[36,147,49],{"class":42},[36,149,150],{"class":52},"template",[36,152,70],{"class":42},[36,154,156,159,162,165,167,170],{"class":38,"line":155},10,[36,157,158],{"class":42},"  \u003C",[36,160,161],{"class":52},"div",[36,163,164],{"class":56}," class",[36,166,63],{"class":42},[36,168,169],{"class":66},"\"wrapper\"",[36,171,70],{"class":42},[36,173,175,178,181,183,185,188,191,193],{"class":38,"line":174},11,[36,176,177],{"class":42},"    \u003C",[36,179,180],{"class":52},"button",[36,182,164],{"class":56},[36,184,63],{"class":42},[36,186,187],{"class":66},"\"submit-btn\"",[36,189,190],{"class":42},">{{ loading ? \"Loading...\" : \"Submit\" }}\u003C\u002F",[36,192,180],{"class":52},[36,194,70],{"class":42},[36,196,198,201,203],{"class":38,"line":197},12,[36,199,200],{"class":42},"  \u003C\u002F",[36,202,161],{"class":52},[36,204,70],{"class":42},[36,206,208,210,212],{"class":38,"line":207},13,[36,209,131],{"class":42},[36,211,150],{"class":52},[36,213,70],{"class":42},[27,215,217],{"className":29,"code":216,"language":31,"meta":32,"style":32},"\u002F* MyForm.vue *\u002F\n\u003Ctemplate>\n  \u003Cform>\n    \u003C!--  -->\n    \u003CCustomButton \u002F>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[22,218,219,224,232,241,246,256,264],{"__ignoreMap":32},[36,220,221],{"class":38,"line":39},[36,222,223],{"class":42},"\u002F* MyForm.vue *\u002F\n",[36,225,226,228,230],{"class":38,"line":46},[36,227,49],{"class":42},[36,229,150],{"class":52},[36,231,70],{"class":42},[36,233,234,236,239],{"class":38,"line":73},[36,235,158],{"class":42},[36,237,238],{"class":52},"form",[36,240,70],{"class":42},[36,242,243],{"class":38,"line":103},[36,244,245],{"class":124},"    \u003C!--  -->\n",[36,247,248,250,253],{"class":38,"line":115},[36,249,177],{"class":42},[36,251,252],{"class":52},"CustomButton",[36,254,255],{"class":42}," \u002F>\n",[36,257,258,260,262],{"class":38,"line":121},[36,259,200],{"class":42},[36,261,238],{"class":52},[36,263,70],{"class":42},[36,265,266,268,270],{"class":38,"line":128},[36,267,131],{"class":42},[36,269,150],{"class":52},[36,271,70],{"class":42},[10,273,274],{},[275,276],"img",{"alt":277,"dataZoomable":32,"height":278,"loading":279,"src":280,"width":281},"Form with Custom Button",468,"lazy","\u002Fcontent\u002Fform.png",626,[10,283,284,285,287,288,291],{},"It's all well and good until you have another form where you'd like to use this\n",[22,286,24],{}," but the marketing team said they want to try out a\ndifferent text for that button instead of 'Submit'. So you're probably thinking,\nthat's fine I can just pass whatever text the button should show as ",[22,289,290],{},"props"," and\nit'll be fine.",[27,293,295],{"className":29,"code":294,"language":31,"meta":32,"style":32},"\u002F* CustomSubmitButton.vue *\u002F\n\u003Cscript setup lang=\"ts\">\nwithDefaults(defineProps\u003C{ loading: boolean; text: string }>(), {\n  loading: false,\n  text: \"Submit\",\n});\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"wrapper\">\n    \u003Cbutton>{{ loading ? \"Loading...\" : text }}\u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[22,296,297,301,317,346,354,364,368,376,380,388,402,415,423],{"__ignoreMap":32},[36,298,299],{"class":38,"line":39},[36,300,43],{"class":42},[36,302,303,305,307,309,311,313,315],{"class":38,"line":46},[36,304,49],{"class":42},[36,306,53],{"class":52},[36,308,57],{"class":56},[36,310,60],{"class":56},[36,312,63],{"class":42},[36,314,67],{"class":66},[36,316,70],{"class":42},[36,318,319,321,323,325,327,329,331,333,336,339,341,344],{"class":38,"line":73},[36,320,76],{"class":56},[36,322,79],{"class":42},[36,324,82],{"class":56},[36,326,85],{"class":42},[36,328,89],{"class":88},[36,330,93],{"class":92},[36,332,97],{"class":96},[36,334,335],{"class":42},"; ",[36,337,338],{"class":88},"text",[36,340,93],{"class":92},[36,342,343],{"class":96}," string",[36,345,100],{"class":42},[36,347,348,350,352],{"class":38,"line":103},[36,349,106],{"class":42},[36,351,109],{"class":96},[36,353,112],{"class":42},[36,355,356,359,362],{"class":38,"line":115},[36,357,358],{"class":42},"  text: ",[36,360,361],{"class":66},"\"Submit\"",[36,363,112],{"class":42},[36,365,366],{"class":38,"line":121},[36,367,118],{"class":42},[36,369,370,372,374],{"class":38,"line":128},[36,371,131],{"class":42},[36,373,53],{"class":52},[36,375,70],{"class":42},[36,377,378],{"class":38,"line":138},[36,379,142],{"emptyLinePlaceholder":141},[36,381,382,384,386],{"class":38,"line":145},[36,383,49],{"class":42},[36,385,150],{"class":52},[36,387,70],{"class":42},[36,389,390,392,394,396,398,400],{"class":38,"line":155},[36,391,158],{"class":42},[36,393,161],{"class":52},[36,395,164],{"class":56},[36,397,63],{"class":42},[36,399,169],{"class":66},[36,401,70],{"class":42},[36,403,404,406,408,411,413],{"class":38,"line":174},[36,405,177],{"class":42},[36,407,180],{"class":52},[36,409,410],{"class":42},">{{ loading ? \"Loading...\" : text }}\u003C\u002F",[36,412,180],{"class":52},[36,414,70],{"class":42},[36,416,417,419,421],{"class":38,"line":197},[36,418,200],{"class":42},[36,420,161],{"class":52},[36,422,70],{"class":42},[36,424,425,427,429],{"class":38,"line":207},[36,426,131],{"class":42},[36,428,150],{"class":52},[36,430,70],{"class":42},[27,432,434],{"className":29,"code":433,"language":31,"meta":32,"style":32},"\u002F* MyForm.vue *\u002F\n\u003Ctemplate>\n  \u003Cform>\n    \u003C!-- Other form fields  -->\n    \u003CCustomButton text=\"Register\" \u002F>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[22,435,436,440,448,456,461,477,485],{"__ignoreMap":32},[36,437,438],{"class":38,"line":39},[36,439,223],{"class":42},[36,441,442,444,446],{"class":38,"line":46},[36,443,49],{"class":42},[36,445,150],{"class":52},[36,447,70],{"class":42},[36,449,450,452,454],{"class":38,"line":73},[36,451,158],{"class":42},[36,453,238],{"class":52},[36,455,70],{"class":42},[36,457,458],{"class":38,"line":103},[36,459,460],{"class":124},"    \u003C!-- Other form fields  -->\n",[36,462,463,465,467,470,472,475],{"class":38,"line":115},[36,464,177],{"class":42},[36,466,252],{"class":52},[36,468,469],{"class":56}," text",[36,471,63],{"class":42},[36,473,474],{"class":66},"\"Register\"",[36,476,255],{"class":42},[36,478,479,481,483],{"class":38,"line":121},[36,480,200],{"class":42},[36,482,238],{"class":52},[36,484,70],{"class":42},[36,486,487,489,491],{"class":38,"line":128},[36,488,131],{"class":42},[36,490,150],{"class":52},[36,492,70],{"class":42},[10,494,495,496,499,500,502],{},"After a while, marketing team comes back to you and say we want to change the\n\"loading\" text too. You can probably add another prop called ",[22,497,498],{},"loadingText"," to\nthe custom button and get away again. But what happens when you have yet another\nform using this ",[22,501,24],{}," where you'd like to show a Spinner\nComponent instead of \"Loading...\" text?",[10,504,505,506,510],{},"We can solve this problem using slots by delegating \"what\" to render to the\nView\u002FParent using the component. You can think of slots as the literal meaning,\n\"slot\". We make a space or a ",[507,508,509],"em",{},"slot"," in the component that gets reused and let\nthe parent fill in that slot. Vue.js comes with a special tag for declaring\nslots:",[27,512,516],{"className":513,"code":514,"language":515,"meta":32,"style":32},"language-html shiki shiki-themes github-dark","\u003Cslot>\u003C\u002Fslot>\n","html",[22,517,518],{"__ignoreMap":32},[36,519,520,522,524,527,529],{"class":38,"line":39},[36,521,49],{"class":42},[36,523,509],{"class":52},[36,525,526],{"class":42},">\u003C\u002F",[36,528,509],{"class":52},[36,530,70],{"class":42},[10,532,533,534,536,537,540,541,544],{},"In our ",[22,535,252],{}," component we can replace the ",[507,538,539],{},"dynamic"," part (\"Loading...\"\nand \"Submit\" texts) into a ",[22,542,543],{},"\u003Cslot>\u003C\u002Fslot>",".",[27,546,548],{"className":29,"code":547,"language":31,"meta":32,"style":32},"\u002F* CustomSubmitButton.vue *\u002F\n\u003Ctemplate>\n  \u003Cdiv class=\"wrapper\">\n    \u003Cbutton class=\"submit-btn\">\n      \u003Cslot>\u003C\u002Fslot>\n      \u003C!-- 👈 this part gets replaced by whatever was passed by the parent -->\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[22,549,550,554,562,576,590,603,608,617,625],{"__ignoreMap":32},[36,551,552],{"class":38,"line":39},[36,553,43],{"class":42},[36,555,556,558,560],{"class":38,"line":46},[36,557,49],{"class":42},[36,559,150],{"class":52},[36,561,70],{"class":42},[36,563,564,566,568,570,572,574],{"class":38,"line":73},[36,565,158],{"class":42},[36,567,161],{"class":52},[36,569,164],{"class":56},[36,571,63],{"class":42},[36,573,169],{"class":66},[36,575,70],{"class":42},[36,577,578,580,582,584,586,588],{"class":38,"line":103},[36,579,177],{"class":42},[36,581,180],{"class":52},[36,583,164],{"class":56},[36,585,63],{"class":42},[36,587,187],{"class":66},[36,589,70],{"class":42},[36,591,592,595,597,599,601],{"class":38,"line":115},[36,593,594],{"class":42},"      \u003C",[36,596,509],{"class":52},[36,598,526],{"class":42},[36,600,509],{"class":52},[36,602,70],{"class":42},[36,604,605],{"class":38,"line":121},[36,606,607],{"class":124},"      \u003C!-- 👈 this part gets replaced by whatever was passed by the parent -->\n",[36,609,610,613,615],{"class":38,"line":128},[36,611,612],{"class":42},"    \u003C\u002F",[36,614,180],{"class":52},[36,616,70],{"class":42},[36,618,619,621,623],{"class":38,"line":138},[36,620,200],{"class":42},[36,622,161],{"class":52},[36,624,70],{"class":42},[36,626,627,629,631],{"class":38,"line":145},[36,628,131],{"class":42},[36,630,150],{"class":52},[36,632,70],{"class":42},[27,634,636],{"className":29,"code":635,"language":31,"meta":32,"style":32},"\u002F* MyForm.vue *\u002F\n\u003Ctemplate>\n  \u003Cform>\n    \u003C!-- Other form fields  -->\n    \u003CCustomButton> Register \u003C\u002FCustomButton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[22,637,638,642,650,658,662,675,683],{"__ignoreMap":32},[36,639,640],{"class":38,"line":39},[36,641,223],{"class":42},[36,643,644,646,648],{"class":38,"line":46},[36,645,49],{"class":42},[36,647,150],{"class":52},[36,649,70],{"class":42},[36,651,652,654,656],{"class":38,"line":73},[36,653,158],{"class":42},[36,655,238],{"class":52},[36,657,70],{"class":42},[36,659,660],{"class":38,"line":103},[36,661,460],{"class":124},[36,663,664,666,668,671,673],{"class":38,"line":115},[36,665,177],{"class":42},[36,667,252],{"class":52},[36,669,670],{"class":42},"> Register \u003C\u002F",[36,672,252],{"class":52},[36,674,70],{"class":42},[36,676,677,679,681],{"class":38,"line":121},[36,678,200],{"class":42},[36,680,238],{"class":52},[36,682,70],{"class":42},[36,684,685,687,689],{"class":38,"line":128},[36,686,131],{"class":42},[36,688,150],{"class":52},[36,690,70],{"class":42},[10,692,693],{},[275,694],{"alt":695,"dataZoomable":32,"height":696,"loading":279,"src":697,"width":698},"CustomButton with Slot content",596,"\u002Fcontent\u002Fregister.png",494,[10,700,701],{},"Now since the form state is managed by the Parent Form component, we can choose\nwhat to show in the CustomButton slot based on the state within the Parent Form\ncomponent. So let's add the Loading Spinner into the slot as well.",[27,703,705],{"className":29,"code":704,"language":31,"meta":32,"style":32},"\u002F* MyForm.vue *\u002F\n\u003Ctemplate>\n  \u003Cform>\n    \u003C!-- Other form fields  -->\n    \u003CCustomButton>\n      \u003C!-- Can be replaced by a loading component -->\n      \u003Ctemplate v-if=\"loading\">Loading...\u003C\u002Ftemplate>\n      \u003Ctemplate v-else>Register\u003C\u002Ftemplate>\n    \u003C\u002FCustomButton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[22,706,707,711,719,727,731,739,744,769,785,793,801],{"__ignoreMap":32},[36,708,709],{"class":38,"line":39},[36,710,223],{"class":42},[36,712,713,715,717],{"class":38,"line":46},[36,714,49],{"class":42},[36,716,150],{"class":52},[36,718,70],{"class":42},[36,720,721,723,725],{"class":38,"line":73},[36,722,158],{"class":42},[36,724,238],{"class":52},[36,726,70],{"class":42},[36,728,729],{"class":38,"line":103},[36,730,460],{"class":124},[36,732,733,735,737],{"class":38,"line":115},[36,734,177],{"class":42},[36,736,252],{"class":52},[36,738,70],{"class":42},[36,740,741],{"class":38,"line":121},[36,742,743],{"class":124},"      \u003C!-- Can be replaced by a loading component -->\n",[36,745,746,748,750,753,755,758,760,762,765,767],{"class":38,"line":128},[36,747,594],{"class":42},[36,749,150],{"class":52},[36,751,752],{"class":92}," v-if",[36,754,63],{"class":42},[36,756,757],{"class":66},"\"",[36,759,89],{"class":42},[36,761,757],{"class":66},[36,763,764],{"class":42},">Loading...\u003C\u002F",[36,766,150],{"class":52},[36,768,70],{"class":42},[36,770,771,773,775,778,781,783],{"class":38,"line":138},[36,772,594],{"class":42},[36,774,150],{"class":52},[36,776,777],{"class":92}," v-else",[36,779,780],{"class":42},">Register\u003C\u002F",[36,782,150],{"class":52},[36,784,70],{"class":42},[36,786,787,789,791],{"class":38,"line":145},[36,788,612],{"class":42},[36,790,252],{"class":52},[36,792,70],{"class":42},[36,794,795,797,799],{"class":38,"line":155},[36,796,200],{"class":42},[36,798,238],{"class":52},[36,800,70],{"class":42},[36,802,803,805,807],{"class":38,"line":174},[36,804,131],{"class":42},[36,806,150],{"class":52},[36,808,70],{"class":42},[14,810,812],{"id":811},"scoped-slots","Scoped Slots",[10,814,815,816,818,819,822,823,826],{},"Slots also provide a very powerful feature of passing value(s) back up to the\nparent component. Let's say we want to access the focus or hover state (or any\ninternal state) of the ",[22,817,252],{}," component in the form, we can do so by\n",[507,820,821],{},"binding"," the property onto the ",[22,824,825],{},"\u003Cslot>"," component.",[27,828,830],{"className":29,"code":829,"language":31,"meta":32,"style":32},"\u002F* CustomSubmitButton.vue *\u002F\n\u003Cscript setup lang=\"ts\">\nimport { ref } from \"vue\";\n\u002F* --- *\u002F\nconst isFocused = ref\u003Cboolean>(false);\n\u003C\u002Fscript>\n\u003Ctemplate>\n  \u003Cdiv class=\"wrapper\">\n    \u003Cbutton\n      @focusout=\"isFocused = false\"\n      @focusin=\"isFocused = true\"\n      class=\"submit-btn\"\n    >\n      \u003Cslot v-bind:isFocused=\"isFocused\">\u003C\u002Fslot>\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[22,831,832,836,852,869,874,901,909,917,931,938,948,958,968,973,994,1003,1012],{"__ignoreMap":32},[36,833,834],{"class":38,"line":39},[36,835,43],{"class":42},[36,837,838,840,842,844,846,848,850],{"class":38,"line":46},[36,839,49],{"class":42},[36,841,53],{"class":52},[36,843,57],{"class":56},[36,845,60],{"class":56},[36,847,63],{"class":42},[36,849,67],{"class":66},[36,851,70],{"class":42},[36,853,854,857,860,863,866],{"class":38,"line":73},[36,855,856],{"class":92},"import",[36,858,859],{"class":42}," { ref } ",[36,861,862],{"class":92},"from",[36,864,865],{"class":66}," \"vue\"",[36,867,868],{"class":42},";\n",[36,870,871],{"class":38,"line":103},[36,872,873],{"class":124},"\u002F* --- *\u002F\n",[36,875,876,879,882,885,888,890,893,896,898],{"class":38,"line":115},[36,877,878],{"class":92},"const",[36,880,881],{"class":96}," isFocused",[36,883,884],{"class":92}," =",[36,886,887],{"class":56}," ref",[36,889,49],{"class":42},[36,891,892],{"class":96},"boolean",[36,894,895],{"class":42},">(",[36,897,109],{"class":96},[36,899,900],{"class":42},");\n",[36,902,903,905,907],{"class":38,"line":121},[36,904,131],{"class":42},[36,906,53],{"class":52},[36,908,70],{"class":42},[36,910,911,913,915],{"class":38,"line":128},[36,912,49],{"class":42},[36,914,150],{"class":52},[36,916,70],{"class":42},[36,918,919,921,923,925,927,929],{"class":38,"line":138},[36,920,158],{"class":42},[36,922,161],{"class":52},[36,924,164],{"class":56},[36,926,63],{"class":42},[36,928,169],{"class":66},[36,930,70],{"class":42},[36,932,933,935],{"class":38,"line":145},[36,934,177],{"class":42},[36,936,937],{"class":52},"button\n",[36,939,940,943,945],{"class":38,"line":155},[36,941,942],{"class":56},"      @focusout",[36,944,63],{"class":42},[36,946,947],{"class":66},"\"isFocused = false\"\n",[36,949,950,953,955],{"class":38,"line":174},[36,951,952],{"class":56},"      @focusin",[36,954,63],{"class":42},[36,956,957],{"class":66},"\"isFocused = true\"\n",[36,959,960,963,965],{"class":38,"line":197},[36,961,962],{"class":56},"      class",[36,964,63],{"class":42},[36,966,967],{"class":66},"\"submit-btn\"\n",[36,969,970],{"class":38,"line":207},[36,971,972],{"class":42},"    >\n",[36,974,976,978,980,983,985,988,990,992],{"class":38,"line":975},14,[36,977,594],{"class":42},[36,979,509],{"class":52},[36,981,982],{"class":56}," v-bind:isFocused",[36,984,63],{"class":42},[36,986,987],{"class":66},"\"isFocused\"",[36,989,526],{"class":42},[36,991,509],{"class":52},[36,993,70],{"class":42},[36,995,997,999,1001],{"class":38,"line":996},15,[36,998,612],{"class":42},[36,1000,180],{"class":52},[36,1002,70],{"class":42},[36,1004,1006,1008,1010],{"class":38,"line":1005},16,[36,1007,200],{"class":42},[36,1009,161],{"class":52},[36,1011,70],{"class":42},[36,1013,1015,1017,1019],{"class":38,"line":1014},17,[36,1016,131],{"class":42},[36,1018,150],{"class":52},[36,1020,70],{"class":42},[27,1022,1024],{"className":29,"code":1023,"language":31,"meta":32,"style":32},"\u002F* MyForm.vue *\u002F\n\u003Ctemplate>\n  \u003Cform>\n    \u003C!-- Other form fields  -->\n    \u003CCustomButton v-slot=\"{ isFocused }\">\n      \u003C!-- Do whatever you want with `isFocused` value in the parent template -->\n      {{ isFocused }}\n      \u003C!-- Can be replaced by a loading component -->\n      \u003Ctemplate v-if=\"loading\">Loading...\u003C\u002Ftemplate>\n      \u003Ctemplate v-else>Register\u003C\u002Ftemplate>\n    \u003C\u002FCustomButton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[22,1025,1026,1030,1038,1046,1050,1070,1075,1080,1084,1106,1120,1128,1136],{"__ignoreMap":32},[36,1027,1028],{"class":38,"line":39},[36,1029,223],{"class":42},[36,1031,1032,1034,1036],{"class":38,"line":46},[36,1033,49],{"class":42},[36,1035,150],{"class":52},[36,1037,70],{"class":42},[36,1039,1040,1042,1044],{"class":38,"line":73},[36,1041,158],{"class":42},[36,1043,238],{"class":52},[36,1045,70],{"class":42},[36,1047,1048],{"class":38,"line":103},[36,1049,460],{"class":124},[36,1051,1052,1054,1056,1059,1061,1063,1066,1068],{"class":38,"line":115},[36,1053,177],{"class":42},[36,1055,252],{"class":52},[36,1057,1058],{"class":56}," v-slot",[36,1060,63],{"class":42},[36,1062,757],{"class":66},[36,1064,1065],{"class":42},"{ isFocused }",[36,1067,757],{"class":66},[36,1069,70],{"class":42},[36,1071,1072],{"class":38,"line":121},[36,1073,1074],{"class":124},"      \u003C!-- Do whatever you want with `isFocused` value in the parent template -->\n",[36,1076,1077],{"class":38,"line":128},[36,1078,1079],{"class":42},"      {{ isFocused }}\n",[36,1081,1082],{"class":38,"line":138},[36,1083,743],{"class":124},[36,1085,1086,1088,1090,1092,1094,1096,1098,1100,1102,1104],{"class":38,"line":145},[36,1087,594],{"class":42},[36,1089,150],{"class":52},[36,1091,752],{"class":92},[36,1093,63],{"class":42},[36,1095,757],{"class":66},[36,1097,89],{"class":42},[36,1099,757],{"class":66},[36,1101,764],{"class":42},[36,1103,150],{"class":52},[36,1105,70],{"class":42},[36,1107,1108,1110,1112,1114,1116,1118],{"class":38,"line":155},[36,1109,594],{"class":42},[36,1111,150],{"class":52},[36,1113,777],{"class":92},[36,1115,780],{"class":42},[36,1117,150],{"class":52},[36,1119,70],{"class":42},[36,1121,1122,1124,1126],{"class":38,"line":174},[36,1123,612],{"class":42},[36,1125,252],{"class":52},[36,1127,70],{"class":42},[36,1129,1130,1132,1134],{"class":38,"line":197},[36,1131,200],{"class":42},[36,1133,238],{"class":52},[36,1135,70],{"class":42},[36,1137,1138,1140,1142],{"class":38,"line":207},[36,1139,131],{"class":42},[36,1141,150],{"class":52},[36,1143,70],{"class":42},[10,1145,1146],{},"We can also wrap all the form related logic into a its own renderless component\nwhich handles submitting form, error states, form field states and expose them\nthrough scoped slots.",[27,1148,1150],{"className":29,"code":1149,"language":31,"meta":32,"style":32},"\u002F* MyForm.vue *\u002F\n\u003Ctemplate>\n  \u003CMyCustomFormWrapper v-slot=\"{ errors, submit, fields }\">\n    \u003C!-- Other form fields  -->\n    \u003Cspan v-if=\"errors.email\" class=\"error\">{{ errors.email }}\u003C\u002Fspan>\n\n    \u003CCustomButton @click=\"submit\" v-slot=\"{ isFocused }\">\n      \u003C!-- Do whatever you want with `isFocused` state in the parent template -->\n      {{ isFocused }}\n      \u003C!-- Can be replaced by a loading component -->\n      \u003Ctemplate v-if=\"loading\">Loading...\u003C\u002Ftemplate>\n      \u003Ctemplate v-else>Register\u003C\u002Ftemplate>\n    \u003C\u002FCustomButton>\n  \u003C\u002FMyCustomFormWrapper>\n\u003C\u002Ftemplate>\n",[22,1151,1152,1156,1164,1184,1188,1215,1219,1252,1257,1261,1265,1287,1301,1309,1317],{"__ignoreMap":32},[36,1153,1154],{"class":38,"line":39},[36,1155,223],{"class":42},[36,1157,1158,1160,1162],{"class":38,"line":46},[36,1159,49],{"class":42},[36,1161,150],{"class":52},[36,1163,70],{"class":42},[36,1165,1166,1168,1171,1173,1175,1177,1180,1182],{"class":38,"line":73},[36,1167,158],{"class":42},[36,1169,1170],{"class":52},"MyCustomFormWrapper",[36,1172,1058],{"class":56},[36,1174,63],{"class":42},[36,1176,757],{"class":66},[36,1178,1179],{"class":42},"{ errors, submit, fields }",[36,1181,757],{"class":66},[36,1183,70],{"class":42},[36,1185,1186],{"class":38,"line":103},[36,1187,460],{"class":124},[36,1189,1190,1192,1194,1196,1198,1201,1203,1205,1208,1211,1213],{"class":38,"line":115},[36,1191,177],{"class":42},[36,1193,36],{"class":52},[36,1195,752],{"class":56},[36,1197,63],{"class":42},[36,1199,1200],{"class":66},"\"errors.email\"",[36,1202,164],{"class":56},[36,1204,63],{"class":42},[36,1206,1207],{"class":66},"\"error\"",[36,1209,1210],{"class":42},">{{ errors.email }}\u003C\u002F",[36,1212,36],{"class":52},[36,1214,70],{"class":42},[36,1216,1217],{"class":38,"line":121},[36,1218,142],{"emptyLinePlaceholder":141},[36,1220,1221,1223,1225,1228,1231,1233,1235,1238,1240,1242,1244,1246,1248,1250],{"class":38,"line":128},[36,1222,177],{"class":42},[36,1224,252],{"class":52},[36,1226,1227],{"class":42}," @",[36,1229,1230],{"class":56},"click",[36,1232,63],{"class":42},[36,1234,757],{"class":66},[36,1236,1237],{"class":42},"submit",[36,1239,757],{"class":66},[36,1241,1058],{"class":56},[36,1243,63],{"class":42},[36,1245,757],{"class":66},[36,1247,1065],{"class":42},[36,1249,757],{"class":66},[36,1251,70],{"class":42},[36,1253,1254],{"class":38,"line":138},[36,1255,1256],{"class":124},"      \u003C!-- Do whatever you want with `isFocused` state in the parent template -->\n",[36,1258,1259],{"class":38,"line":145},[36,1260,1079],{"class":42},[36,1262,1263],{"class":38,"line":155},[36,1264,743],{"class":124},[36,1266,1267,1269,1271,1273,1275,1277,1279,1281,1283,1285],{"class":38,"line":174},[36,1268,594],{"class":42},[36,1270,150],{"class":52},[36,1272,752],{"class":92},[36,1274,63],{"class":42},[36,1276,757],{"class":66},[36,1278,89],{"class":42},[36,1280,757],{"class":66},[36,1282,764],{"class":42},[36,1284,150],{"class":52},[36,1286,70],{"class":42},[36,1288,1289,1291,1293,1295,1297,1299],{"class":38,"line":197},[36,1290,594],{"class":42},[36,1292,150],{"class":52},[36,1294,777],{"class":92},[36,1296,780],{"class":42},[36,1298,150],{"class":52},[36,1300,70],{"class":42},[36,1302,1303,1305,1307],{"class":38,"line":207},[36,1304,612],{"class":42},[36,1306,252],{"class":52},[36,1308,70],{"class":42},[36,1310,1311,1313,1315],{"class":38,"line":975},[36,1312,200],{"class":42},[36,1314,1170],{"class":52},[36,1316,70],{"class":42},[36,1318,1319,1321,1323],{"class":38,"line":996},[36,1320,131],{"class":42},[36,1322,150],{"class":52},[36,1324,70],{"class":42},[10,1326,1327,1328,112,1335,112,1340,1345],{},"This pattern is pretty common in libraries like ",[1329,1330,1334],"a",{"href":1331,"rel":1332},"https:\u002F\u002Fvueuse.org\u002F",[1333],"nofollow","VueUse",[1329,1336,1339],{"href":1337,"rel":1338},"https:\u002F\u002Fvee-validate.logaretm.com\u002Fv4\u002F",[1333],"vee-validate",[1329,1341,1344],{"href":1342,"rel":1343},"https:\u002F\u002Fheadlessui.com\u002F",[1333],"Headless UI"," and many more, since it allows us to\nbuild highly reusable renderless components.",[10,1347,1348],{},"Further Reading \u002F References:",[1350,1351,1352],"ul",{},[1353,1354,1355],"li",{},[1329,1356,1359],{"href":1357,"rel":1358},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fcomponents\u002Fslots.html#slots",[1333],"Vue.js Slots",[1361,1362,1363],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}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);}",{"title":32,"searchDepth":46,"depth":46,"links":1365},[1366,1367],{"id":16,"depth":46,"text":17},{"id":811,"depth":46,"text":812},"2021-09-12","Slots let a parent decide what a Vue component renders, and scoped slots pass data back so the parent can render it. A worked example builds a renderless submit button.",false,"md","2026-08-29T17:42:32+07:00",{},"\u002Fblog\u002Fusing-slots-in-vue","5 min read",{"title":5,"description":1369},"blog\u002Fusing-slots-in-vue","FlZdMdCRHZwBorqQe0LVdCxWv1_PzKSu-E8Gwl8g1po",1788101292793]