[{"data":1,"prerenderedAt":1193},["ShallowReactive",2],{"i-simple-icons:github":3,"i-simple-icons:linkedin":8,"blog-\u002Fblog\u002Fun-bouton-qui-repond":11,"blog-others-\u002Fblog\u002Fun-bouton-qui-repond":862},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":7},0,24,false,"\u003Cpath fill=\"currentColor\" d=\"M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":9,"hidden":10},"\u003Cpath fill=\"currentColor\" d=\"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037c-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85c3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.06 2.06 0 0 1-2.063-2.065a2.064 2.064 0 1 1 2.063 2.065m1.782 13.019H3.555V9h3.564zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0z\"\u002F>",true,{"id":12,"title":13,"body":14,"date":847,"description":848,"extension":849,"meta":850,"navigation":10,"path":851,"readingTime":123,"seo":852,"seoTitle":853,"stem":854,"tags":855,"tone":859,"updated":860,"__hash__":861},"blog\u002Fblog\u002Fun-bouton-qui-repond.md","Un bouton doit répondre avant qu’on le relâche",{"type":15,"value":16,"toc":839},"minimark",[17,21,24,29,37,44,47,50,54,61,147,150,154,157,339,342,372,376,381,384,481,487,499,503,506,509,804,807,825,829,832,835],[18,19,20],"p",{},"Appuyez sur un bouton d’iOS. Il réagit au moment où le doigt le touche, pas quand il le quitte. Cette différence tient en quelques dizaines de millisecondes, et pourtant c’est elle qui sépare une interface qui écoute d’une interface qui attend.",[18,22,23],{},"La plupart des sites réagissent trop tard. Pas parce que c’est difficile à corriger, mais parce que personne ne le remarque consciemment. On sent juste que « c’est mou », sans savoir pourquoi.",[25,26,28],"h2",{"id":27},"le-clic-arrive-trop-tard","Le clic arrive trop tard",[18,30,31,32,36],{},"L’événement ",[33,34,35],"code",{},"click"," se déclenche au relâchement. Si c’est là que le bouton change d’état, l’utilisateur a déjà appuyé, attendu et relâché sans rien voir. Sur mobile s’ajoute le délai du système, et l’écran paraît figé le temps d’un battement.",[18,38,39,40,43],{},"Le retour visuel doit vivre sur ",[33,41,42],{},"pointerdown",", l’instant du contact. Le clic, lui, garde son rôle : déclencher l’action. Deux événements, deux responsabilités.",[18,45,46],{},"Essayez les deux versions ci-dessous, à la souris ou au doigt. La différence se sent plus qu’elle ne se voit.",[48,49],"demo-press",{},[25,51,53],{"id":52},"la-version-la-plus-courte-css","La version la plus courte : CSS",[18,55,56,57,60],{},"Avant d’écrire du JavaScript, il y a ",[33,58,59],{},":active",". Le navigateur l’applique dès l’appui, à la souris comme au doigt, et aussi au clavier quand on enfonce la barre d’espace :",[62,63,69],"pre",{"className":64,"code":65,"filename":66,"language":67,"meta":68,"style":68},"language-css shiki shiki-themes github-light github-dark",".button {\n  transition: transform 120ms ease-out;\n}\n.button:active {\n  transform: scale(0.97);\n}\n","button.css","css","",[33,70,71,84,107,113,121,142],{"__ignoreMap":68},[72,73,76,80],"span",{"class":74,"line":75},"line",1,[72,77,79],{"class":78},"sScJk",".button",[72,81,83],{"class":82},"sVt8B"," {\n",[72,85,87,91,94,97,101,104],{"class":74,"line":86},2,[72,88,90],{"class":89},"sj4cs","  transition",[72,92,93],{"class":82},": transform ",[72,95,96],{"class":89},"120",[72,98,100],{"class":99},"szBVR","ms",[72,102,103],{"class":89}," ease-out",[72,105,106],{"class":82},";\n",[72,108,110],{"class":74,"line":109},3,[72,111,112],{"class":82},"}\n",[72,114,116,119],{"class":74,"line":115},4,[72,117,118],{"class":78},".button:active",[72,120,83],{"class":82},[72,122,124,127,130,133,136,139],{"class":74,"line":123},5,[72,125,126],{"class":89},"  transform",[72,128,129],{"class":82},": ",[72,131,132],{"class":89},"scale",[72,134,135],{"class":82},"(",[72,137,138],{"class":89},"0.97",[72,140,141],{"class":82},");\n",[72,143,145],{"class":74,"line":144},6,[72,146,112],{"class":82},[18,148,149],{},"Pour un bouton simple, c’est tout ce qu’il faut, et c’est ce que j’utilise dans la majorité des cas. Zéro code, zéro état à gérer, et ça marche sans JavaScript.",[25,151,153],{"id":152},"la-version-vue-quand-il-faut-un-état","La version Vue, quand il faut un état",[18,155,156],{},"Parfois l’appui déclenche autre chose qu’un style : un son, une vibration, un retour sur un élément voisin. Là, il faut l’événement :",[62,158,163],{"className":159,"code":160,"filename":161,"language":162,"meta":68,"style":68},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup lang=\"ts\">\nconst pressed = ref(false)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cbutton\n    class=\"transition-transform duration-100 ease-out\"\n    :class=\"pressed && 'scale-[0.97]'\"\n    @pointerdown=\"pressed = true\"\n    @pointerup=\"pressed = false\"\n    @pointerleave=\"pressed = false\"\n    @pointercancel=\"pressed = false\"\n  >\n    Enregistrer\n  \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n","PressButton.vue","vue",[33,164,165,190,212,221,226,235,243,254,265,276,287,297,307,313,319,330],{"__ignoreMap":68},[72,166,167,170,174,177,180,183,187],{"class":74,"line":75},[72,168,169],{"class":82},"\u003C",[72,171,173],{"class":172},"s9eBZ","script",[72,175,176],{"class":78}," setup",[72,178,179],{"class":78}," lang",[72,181,182],{"class":82},"=",[72,184,186],{"class":185},"sZZnC","\"ts\"",[72,188,189],{"class":82},">\n",[72,191,192,195,198,201,204,206,209],{"class":74,"line":86},[72,193,194],{"class":99},"const",[72,196,197],{"class":89}," pressed",[72,199,200],{"class":99}," =",[72,202,203],{"class":78}," ref",[72,205,135],{"class":82},[72,207,208],{"class":89},"false",[72,210,211],{"class":82},")\n",[72,213,214,217,219],{"class":74,"line":109},[72,215,216],{"class":82},"\u003C\u002F",[72,218,173],{"class":172},[72,220,189],{"class":82},[72,222,223],{"class":74,"line":115},[72,224,225],{"emptyLinePlaceholder":10},"\n",[72,227,228,230,233],{"class":74,"line":123},[72,229,169],{"class":82},[72,231,232],{"class":172},"template",[72,234,189],{"class":82},[72,236,237,240],{"class":74,"line":144},[72,238,239],{"class":82},"  \u003C",[72,241,242],{"class":172},"button\n",[72,244,246,249,251],{"class":74,"line":245},7,[72,247,248],{"class":78},"    class",[72,250,182],{"class":82},[72,252,253],{"class":185},"\"transition-transform duration-100 ease-out\"\n",[72,255,257,260,262],{"class":74,"line":256},8,[72,258,259],{"class":78},"    :class",[72,261,182],{"class":82},[72,263,264],{"class":185},"\"pressed && 'scale-[0.97]'\"\n",[72,266,268,271,273],{"class":74,"line":267},9,[72,269,270],{"class":78},"    @pointerdown",[72,272,182],{"class":82},[72,274,275],{"class":185},"\"pressed = true\"\n",[72,277,279,282,284],{"class":74,"line":278},10,[72,280,281],{"class":78},"    @pointerup",[72,283,182],{"class":82},[72,285,286],{"class":185},"\"pressed = false\"\n",[72,288,290,293,295],{"class":74,"line":289},11,[72,291,292],{"class":78},"    @pointerleave",[72,294,182],{"class":82},[72,296,286],{"class":185},[72,298,300,303,305],{"class":74,"line":299},12,[72,301,302],{"class":78},"    @pointercancel",[72,304,182],{"class":82},[72,306,286],{"class":185},[72,308,310],{"class":74,"line":309},13,[72,311,312],{"class":82},"  >\n",[72,314,316],{"class":74,"line":315},14,[72,317,318],{"class":82},"    Enregistrer\n",[72,320,322,325,328],{"class":74,"line":321},15,[72,323,324],{"class":82},"  \u003C\u002F",[72,326,327],{"class":172},"button",[72,329,189],{"class":82},[72,331,333,335,337],{"class":74,"line":332},16,[72,334,216],{"class":82},[72,336,232],{"class":172},[72,338,189],{"class":82},[18,340,341],{},"Trois détails comptent :",[343,344,345,353,359],"ul",{},[346,347,348,352],"li",{},[349,350,351],"strong",{},"L’échelle reste subtile."," Entre 0,95 et 0,98. En dessous, le bouton semble s’enfoncer dans la page, et sur un petit élément le texte devient flou le temps de l’animation.",[346,354,355,358],{},[349,356,357],{},"La durée est courte."," 100 à 160 ms. Plus long, et le retour arrive après la sensation, ce qui est pire que rien.",[346,360,361,371],{},[349,362,363,366,367,370],{},[33,364,365],{},"pointerleave"," et ",[33,368,369],{},"pointercancel"," remettent l’état."," Sinon le bouton reste enfoncé quand le doigt glisse en dehors, ou quand le système interrompt le geste pour un défilement.",[25,373,375],{"id":374},"et-au-clavier","Et au clavier ?",[18,377,378,380],{},[33,379,42],{}," ne se déclenche pas quand on valide avec Entrée ou Espace. Un bouton qui ne réagit qu’au pointeur est muet pour tout le monde au clavier, ce qui inclut beaucoup de gens qui utilisent un site toute la journée.",[18,382,383],{},"Si le retour passe par du JavaScript, j’écoute aussi le clavier :",[62,385,387],{"className":159,"code":386,"filename":161,"language":162,"meta":68,"style":68},"\u003Cbutton\n  @keydown.space.enter=\"pressed = true\"\n  @keyup.space.enter=\"pressed = false\"\n  @blur=\"pressed = false\"\n>\n",[33,388,389,395,430,458,477],{"__ignoreMap":68},[72,390,391,393],{"class":74,"line":75},[72,392,169],{"class":82},[72,394,242],{"class":172},[72,396,397,400,403,406,409,411,414,416,419,422,424,427],{"class":74,"line":86},[72,398,399],{"class":82},"  @",[72,401,402],{"class":78},"keydown",[72,404,405],{"class":82},".",[72,407,408],{"class":78},"space",[72,410,405],{"class":82},[72,412,413],{"class":78},"enter",[72,415,182],{"class":82},[72,417,418],{"class":185},"\"",[72,420,421],{"class":82},"pressed ",[72,423,182],{"class":99},[72,425,426],{"class":89}," true",[72,428,429],{"class":185},"\"\n",[72,431,432,434,437,439,441,443,445,447,449,451,453,456],{"class":74,"line":109},[72,433,399],{"class":82},[72,435,436],{"class":78},"keyup",[72,438,405],{"class":82},[72,440,408],{"class":78},[72,442,405],{"class":82},[72,444,413],{"class":78},[72,446,182],{"class":82},[72,448,418],{"class":185},[72,450,421],{"class":82},[72,452,182],{"class":99},[72,454,455],{"class":89}," false",[72,457,429],{"class":185},[72,459,460,462,465,467,469,471,473,475],{"class":74,"line":115},[72,461,399],{"class":82},[72,463,464],{"class":78},"blur",[72,466,182],{"class":82},[72,468,418],{"class":185},[72,470,421],{"class":82},[72,472,182],{"class":99},[72,474,455],{"class":89},[72,476,429],{"class":185},[72,478,479],{"class":74,"line":123},[72,480,189],{"class":82},[18,482,483,484,486],{},"Le ",[33,485,464],{}," évite un bouton coincé en position appuyée si le focus part pendant l’appui. Et le contour de focus reste visible : il ne bouge pas avec l’échelle, parce que la transformation s’applique à l’élément entier, contour compris. C’est un point de plus pour une échelle légère.",[488,489,492],"callout",{"title":490,"type":491},"Et les animations réduites ?","note",[18,493,494,495,498],{},"Une échelle de 3 % sur 120 ms ne déclenche pas de gêne vestibulaire : ce n’est pas un mouvement dans l’espace, c’est un état. Je la garde même quand ",[33,496,497],{},"prefers-reduced-motion"," est actif. Ce que je retire dans ce cas, ce sont les déplacements, les rebonds et les transitions longues, pas le retour à l’appui.",[25,500,502],{"id":501},"la-confirmation-cest-un-autre-moment","La confirmation, c’est un autre moment",[18,504,505],{},"Le retour à la pression dit « je t’ai entendu ». La confirmation dit « c’est fait ». Ce sont deux moments, et ils méritent deux traitements : un rapide sur le bouton, un plus posé pour le message qui suit.",[18,507,508],{},"Pour une action qui prend du temps, le bouton passe par trois états, et chacun se voit :",[62,510,513],{"className":159,"code":511,"filename":512,"language":162,"meta":68,"style":68},"\u003Cscript setup lang=\"ts\">\nconst status = ref\u003C'idle' | 'saving' | 'saved'>('idle')\n\nasync function save() {\n  status.value = 'saving'\n  await api.save()\n  status.value = 'saved'\n  setTimeout(() => (status.value = 'idle'), 1500)\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cbutton :disabled=\"status === 'saving'\" :aria-busy=\"status === 'saving'\" @click=\"save\">\n    \u003Cspan v-if=\"status === 'saving'\">Enregistrement…\u003C\u002Fspan>\n    \u003Cspan v-else-if=\"status === 'saved'\">Enregistré\u003C\u002Fspan>\n    \u003Cspan v-else>Enregistrer\u003C\u002Fspan>\n  \u003C\u002Fbutton>\n  \u003Cp role=\"status\" class=\"sr-only\">{{ status === 'saved' ? 'Modifications enregistrées' : '' }}\u003C\u002Fp>\n\u003C\u002Ftemplate>\n","SaveButton.vue",[33,514,515,531,565,569,583,593,607,616,643,647,655,659,667,698,719,740,756,765,795],{"__ignoreMap":68},[72,516,517,519,521,523,525,527,529],{"class":74,"line":75},[72,518,169],{"class":82},[72,520,173],{"class":172},[72,522,176],{"class":78},[72,524,179],{"class":78},[72,526,182],{"class":82},[72,528,186],{"class":185},[72,530,189],{"class":82},[72,532,533,535,538,540,542,544,547,550,553,555,558,561,563],{"class":74,"line":86},[72,534,194],{"class":99},[72,536,537],{"class":89}," status",[72,539,200],{"class":99},[72,541,203],{"class":78},[72,543,169],{"class":82},[72,545,546],{"class":185},"'idle'",[72,548,549],{"class":99}," |",[72,551,552],{"class":185}," 'saving'",[72,554,549],{"class":99},[72,556,557],{"class":185}," 'saved'",[72,559,560],{"class":82},">(",[72,562,546],{"class":185},[72,564,211],{"class":82},[72,566,567],{"class":74,"line":109},[72,568,225],{"emptyLinePlaceholder":10},[72,570,571,574,577,580],{"class":74,"line":115},[72,572,573],{"class":99},"async",[72,575,576],{"class":99}," function",[72,578,579],{"class":78}," save",[72,581,582],{"class":82},"() {\n",[72,584,585,588,590],{"class":74,"line":123},[72,586,587],{"class":82},"  status.value ",[72,589,182],{"class":99},[72,591,592],{"class":185}," 'saving'\n",[72,594,595,598,601,604],{"class":74,"line":144},[72,596,597],{"class":99},"  await",[72,599,600],{"class":82}," api.",[72,602,603],{"class":78},"save",[72,605,606],{"class":82},"()\n",[72,608,609,611,613],{"class":74,"line":245},[72,610,587],{"class":82},[72,612,182],{"class":99},[72,614,615],{"class":185}," 'saved'\n",[72,617,618,621,624,627,630,632,635,638,641],{"class":74,"line":256},[72,619,620],{"class":78},"  setTimeout",[72,622,623],{"class":82},"(() ",[72,625,626],{"class":99},"=>",[72,628,629],{"class":82}," (status.value ",[72,631,182],{"class":99},[72,633,634],{"class":185}," 'idle'",[72,636,637],{"class":82},"), ",[72,639,640],{"class":89},"1500",[72,642,211],{"class":82},[72,644,645],{"class":74,"line":267},[72,646,112],{"class":82},[72,648,649,651,653],{"class":74,"line":278},[72,650,216],{"class":82},[72,652,173],{"class":172},[72,654,189],{"class":82},[72,656,657],{"class":74,"line":289},[72,658,225],{"emptyLinePlaceholder":10},[72,660,661,663,665],{"class":74,"line":299},[72,662,169],{"class":82},[72,664,232],{"class":172},[72,666,189],{"class":82},[72,668,669,671,673,676,678,681,684,686,688,691,693,696],{"class":74,"line":309},[72,670,239],{"class":82},[72,672,327],{"class":172},[72,674,675],{"class":78}," :disabled",[72,677,182],{"class":82},[72,679,680],{"class":185},"\"status === 'saving'\"",[72,682,683],{"class":78}," :aria-busy",[72,685,182],{"class":82},[72,687,680],{"class":185},[72,689,690],{"class":78}," @click",[72,692,182],{"class":82},[72,694,695],{"class":185},"\"save\"",[72,697,189],{"class":82},[72,699,700,703,705,708,710,712,715,717],{"class":74,"line":315},[72,701,702],{"class":82},"    \u003C",[72,704,72],{"class":172},[72,706,707],{"class":78}," v-if",[72,709,182],{"class":82},[72,711,680],{"class":185},[72,713,714],{"class":82},">Enregistrement…\u003C\u002F",[72,716,72],{"class":172},[72,718,189],{"class":82},[72,720,721,723,725,728,730,733,736,738],{"class":74,"line":321},[72,722,702],{"class":82},[72,724,72],{"class":172},[72,726,727],{"class":78}," v-else-if",[72,729,182],{"class":82},[72,731,732],{"class":185},"\"status === 'saved'\"",[72,734,735],{"class":82},">Enregistré\u003C\u002F",[72,737,72],{"class":172},[72,739,189],{"class":82},[72,741,742,744,746,749,752,754],{"class":74,"line":332},[72,743,702],{"class":82},[72,745,72],{"class":172},[72,747,748],{"class":78}," v-else",[72,750,751],{"class":82},">Enregistrer\u003C\u002F",[72,753,72],{"class":172},[72,755,189],{"class":82},[72,757,759,761,763],{"class":74,"line":758},17,[72,760,324],{"class":82},[72,762,327],{"class":172},[72,764,189],{"class":82},[72,766,768,770,772,775,777,780,783,785,788,791,793],{"class":74,"line":767},18,[72,769,239],{"class":82},[72,771,18],{"class":172},[72,773,774],{"class":78}," role",[72,776,182],{"class":82},[72,778,779],{"class":185},"\"status\"",[72,781,782],{"class":78}," class",[72,784,182],{"class":82},[72,786,787],{"class":185},"\"sr-only\"",[72,789,790],{"class":82},">{{ status === 'saved' ? 'Modifications enregistrées' : '' }}\u003C\u002F",[72,792,18],{"class":172},[72,794,189],{"class":82},[72,796,798,800,802],{"class":74,"line":797},19,[72,799,216],{"class":82},[72,801,232],{"class":172},[72,803,189],{"class":82},[18,805,806],{},"Deux choses à ne pas oublier :",[343,808,809,815],{},[346,810,811,814],{},[349,812,813],{},"Le bouton garde sa largeur."," Si « Enregistrement… » est plus long que « Enregistrer », le bouton s’élargit et tout ce qui est à côté saute. Une largeur minimale, ou un libellé de même longueur, règle le problème.",[346,816,817,820,821,824],{},[349,818,819],{},"Le message existe aussi pour un lecteur d’écran."," La couleur qui passe au vert ne dit rien à qui ne la voit pas. Une zone ",[33,822,823],{},"role=\"status\""," annonce la confirmation sans voler le focus.",[25,826,828],{"id":827},"ce-que-ça-change","Ce que ça change",[18,830,831],{},"Pris un par un, ces détails ne se remarquent pas. Personne ne dira « ce bouton répond 80 ms plus tôt ». Mais mis bout à bout, sur tous les boutons, tous les liens, tous les champs, ils font la différence entre un site qu’on trouve agréable sans savoir pourquoi et un site qu’on trouve lent sans savoir pourquoi.",[18,833,834],{},"Quand les deux moments sont là, le retour et la confirmation, personne ne les remarque. C’est le but.",[836,837,838],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":68,"searchDepth":86,"depth":86,"links":840},[841,842,843,844,845,846],{"id":27,"depth":86,"text":28},{"id":52,"depth":86,"text":53},{"id":152,"depth":86,"text":153},{"id":374,"depth":86,"text":375},{"id":501,"depth":86,"text":502},{"id":827,"depth":86,"text":828},"2026-08-19","Le retour à la pression, pas au clic. Pourquoi quelques millisecondes changent la sensation d’une interface, comment je le code en Vue, et ce qu’il faut prévoir pour le clavier et la confirmation.","md",{},"\u002Fblog\u002Fun-bouton-qui-repond",{"title":13,"description":848},"Bouton réactif en Vue : le retour visuel sur pointerdown","blog\u002Fun-bouton-qui-repond",[856,857,858],"Interaction","Vue","Motion","lavender",null,"1LE7bi6hlxlq74LGRqx7RGEnQGk3OCHGiWcRtu9kuzk",[863,1022],{"id":864,"title":865,"body":866,"date":1010,"description":1011,"extension":849,"meta":1012,"navigation":10,"path":1013,"readingTime":115,"seo":1014,"seoTitle":1015,"stem":1016,"tags":1017,"tone":859,"updated":860,"__hash__":1021},"blog\u002Fblog\u002Fcoder-avec-une-ia-sans-perdre-la-main.md","Coder avec une IA sans perdre la main",{"type":15,"value":867,"toc":1003},[868,871,874,878,881,884,887,891,897,903,909,915,919,925,931,937,949,953,986,993,997,1000],[18,869,870],{},"Il y a deux façons de rater le virage des assistants de code. La première est de les ignorer et de taper à la main ce qu’une machine écrit mieux et plus vite. La seconde est de tout leur laisser et de ne plus savoir ce qui tourne en production. Entre les deux, il y a une pratique, et elle se construit.",[18,872,873],{},"Voici la mienne, telle qu’elle est aujourd’hui, après un an de projets livrés avec Claude Code dans l’éditeur.",[25,875,877],{"id":876},"une-boucle-pas-une-baguette","Une boucle, pas une baguette",[18,879,880],{},"L’erreur la plus courante est de voir l’IA comme un générateur : on décrit, on reçoit, on colle. Ce n’est pas comme ça que ça marche bien. Ce qui marche, c’est une boucle courte où chaque étape a un responsable.",[882,883],"demo-loop",{},[18,885,886],{},"L’IA n’intervient qu’à une étape. Tout le reste, c’est du travail d’ingénierie classique, et c’est lui qui fait la qualité du résultat. Si le brief est flou, la génération est floue. Si la relecture est bâclée, les bugs passent. Si les tests n’existent pas, personne ne sait si la troisième itération a cassé la première.",[25,888,890],{"id":889},"ce-que-je-lui-confie","Ce que je lui confie",[18,892,893,896],{},[349,894,895],{},"Le code qui a déjà été écrit mille fois."," Un formulaire avec validation, un composant de liste avec ses états, une fonction qui formate des dates en français, la configuration d’un module Nuxt. L’IA connaît ces motifs par cœur et les écrit sans faute de frappe. Je gagne vingt minutes par occurrence, et il y en a beaucoup dans une journée.",[18,898,899,902],{},[349,900,901],{},"Les tâches larges et peu risquées."," Renommer un composant dans tout le projet, extraire un composable, ajouter un type manquant, écrire les premiers tests d’un fichier. Ce sont des tâches où l’erreur se voit tout de suite.",[18,904,905,908],{},[349,906,907],{},"La première version de quelque chose."," Quand je ne sais pas encore à quoi va ressembler un écran, je lui demande trois propositions. Je n’en garde jamais une telle quelle, mais je choisis plus vite.",[18,910,911,914],{},[349,912,913],{},"Les explications."," Un code hérité que personne ne commente, une erreur de build incompréhensible, une API que je n’ai jamais utilisée. Demander « explique-moi ce fichier » avant de le modifier m’a évité pas mal de casse.",[25,916,918],{"id":917},"ce-que-je-garde","Ce que je garde",[18,920,921,924],{},[349,922,923],{},"Les décisions."," L’architecture, le découpage en composants, le choix d’une bibliothèque, ce qui va dans le state global et ce qui n’y va pas. L’IA peut argumenter, elle ne tranche pas. C’est moi qui porterai la décision dans six mois.",[18,926,927,930],{},[349,928,929],{},"La relecture, ligne par ligne."," Je ne fusionne rien que je n’ai pas lu. Pas survolé : lu. Si une ligne me paraît magique, je demande pourquoi elle est là, et si la réponse ne me convainc pas, elle saute. Un code que je ne comprends pas est un code que je ne pourrai pas corriger à deux heures du matin.",[18,932,933,936],{},[349,934,935],{},"Tout ce qui touche à la sécurité et à l’argent."," Authentification, paiement, droits d’accès, données personnelles. L’IA écrit ce code aussi bien qu’un autre, mais là, je vérifie deux fois plus, et j’écris les tests moi-même.",[18,938,939,942,943,948],{},[349,940,941],{},"L’interface finale."," Les détails qu’un humain remarque sans savoir les nommer, j’en parle dans ",[944,945,947],"a",{"href":946},"\u002Fblog\u002Fce-que-l-ia-ne-voit-pas-dans-une-interface","un autre article",". Ils ne viennent pas de la génération, ils viennent de la relecture avec les yeux de l’utilisateur.",[25,950,952],{"id":951},"les-règles-que-je-me-suis-fixées","Les règles que je me suis fixées",[954,955,956,962,968,974,980],"ol",{},[346,957,958,961],{},[349,959,960],{},"Un fichier de conventions à la racine du projet."," Nom des composants, structure des dossiers, langue des commentaires, format des commits. L’assistant le lit à chaque session, et le code qu’il produit ressemble au mien. Sans ce fichier, chaque génération part dans une direction différente.",[346,963,964,967],{},[349,965,966],{},"Une tâche à la fois."," « Ajoute le champ téléphone au formulaire de contact, avec la validation et le test » est une tâche. « Améliore le formulaire » n’en est pas une. Plus la demande est bornée, plus la réponse est juste, et plus la relecture est rapide.",[346,969,970,973],{},[349,971,972],{},"Les tests tournent avant que je regarde."," Si la suite Cypress est rouge, je ne lis même pas le diff. Ça m’oblige à garder des tests fiables, et ça m’évite de perdre du temps sur un code qui ne marche pas.",[346,975,976,979],{},[349,977,978],{},"Je relis dans l’éditeur, pas dans la conversation."," Le diff dans Git, avec la coloration, le contexte autour, et la possibilité de lancer le projet. La conversation sert à discuter, pas à valider.",[346,981,982,985],{},[349,983,984],{},"Je garde une trace de ce qui a été généré."," Pas pour m’en excuser, mais parce que si un bug apparaît dans trois semaines, savoir qu’un bloc est sorti d’une génération rapide oriente la recherche.",[488,987,990],{"title":988,"type":989},"Le test du lendemain","tip",[18,991,992],{},"Avant de fusionner un gros morceau généré, je me pose une question simple : si cette fonction casse demain et que je n’ai plus accès à l’assistant, est-ce que je sais la réparer ? Si la réponse est non, ce n’est pas prêt.",[25,994,996],{"id":995},"ce-que-ça-change-concrètement","Ce que ça change, concrètement",[18,998,999],{},"Je livre plus vite, c’est vrai, mais ce n’est pas le plus intéressant. Ce qui a changé, c’est où va mon attention. Moins de temps sur la syntaxe et la plomberie, plus de temps sur ce que l’utilisateur voit et ressent. Plus de temps aussi sur les tests, parce qu’ils sont devenus la vraie garantie, celle qui ne dépend pas de qui a écrit le code.",[18,1001,1002],{},"Et je comprends toujours ce que je livre. C’était la condition, et elle tient.",{"title":68,"searchDepth":86,"depth":86,"links":1004},[1005,1006,1007,1008,1009],{"id":876,"depth":86,"text":877},{"id":889,"depth":86,"text":890},{"id":917,"depth":86,"text":918},{"id":951,"depth":86,"text":952},{"id":995,"depth":86,"text":996},"2026-10-21","J’utilise Claude Code tous les jours. Voici ce que je lui confie, ce que je garde, et les règles qui m’évitent de livrer du code que je ne comprends pas.",{},"\u002Fblog\u002Fcoder-avec-une-ia-sans-perdre-la-main",{"title":865,"description":1011},"Coder avec Claude Code sans perdre la main : ma méthode","blog\u002Fcoder-avec-une-ia-sans-perdre-la-main",[1018,1019,1020],"IA","Méthode","Claude Code","v2_5veRBSuqiUYQJU5moplrHaQM26kKEITUIO4IdjOU",{"id":1023,"title":1024,"body":1025,"date":1182,"description":1183,"extension":849,"meta":1184,"navigation":10,"path":946,"readingTime":115,"seo":1185,"seoTitle":1186,"stem":1187,"tags":1188,"tone":1191,"updated":860,"__hash__":1192},"blog\u002Fblog\u002Fce-que-l-ia-ne-voit-pas-dans-une-interface.md","Ce qu’une IA ne voit pas dans une interface",{"type":15,"value":1026,"toc":1173},[1027,1030,1033,1037,1040,1043,1046,1072,1076,1079,1086,1090,1101,1111,1115,1118,1121,1125,1128,1131,1135,1138,1143,1147,1150,1170],[18,1028,1029],{},"Demandez à un assistant de code « une page de liste de projets avec un bouton pour en créer un ». Vous aurez une page propre, des cartes alignées, un bouton bleu. Elle marchera. Et elle sera incomplète d’une façon que personne ne remarque dans la conversation, mais que tout le monde remarque en l’utilisant.",[18,1031,1032],{},"Ce n’est pas une critique des outils. C’est la nature du problème : une interface se juge dans la main, pas sur une capture d’écran. Voici ce que je regarde systématiquement sur un écran généré, et que je trouve presque toujours à corriger.",[25,1034,1036],{"id":1035},"les-états-que-personne-na-demandés","Les états que personne n’a demandés",[18,1038,1039],{},"Un écran n’existe pas dans un seul état. Il se charge, il peut être vide, il peut échouer, et seulement ensuite il montre quelque chose. Une génération produit l’état « succès » parce que c’est celui du brief. Les trois autres restent à écrire.",[1041,1042],"demo-states",{},[18,1044,1045],{},"Pour chacun, la question est la même : qu’est-ce que la personne comprend, et qu’est-ce qu’elle peut faire ?",[343,1047,1048,1054,1060,1066],{},[346,1049,1050,1053],{},[349,1051,1052],{},"Chargement"," : montrer la forme de ce qui arrive, pas un spinner au milieu du vide. Un squelette garde la page stable et dit « ça vient ».",[346,1055,1056,1059],{},[349,1057,1058],{},"Vide"," : ce n’est pas une erreur. Dire qu’il n’y a rien encore, et proposer la première action.",[346,1061,1062,1065],{},[349,1063,1064],{},"Erreur"," : dire ce qui s’est passé sans jargon, rassurer sur ce qui n’a pas été perdu, et offrir une sortie. « Une erreur est survenue » n’aide personne.",[346,1067,1068,1071],{},[349,1069,1070],{},"Succès"," : c’est le seul qu’on obtient gratuitement.",[25,1073,1075],{"id":1074},"le-retour-à-laction","Le retour à l’action",[18,1077,1078],{},"Un bouton généré change de couleur au survol, et c’est tout. Il ne réagit pas à l’appui, il ne confirme rien, il ne dit pas si l’action est en cours. Sur mobile, où il n’y a pas de survol, il ne donne aucun signe de vie.",[18,1080,1081,1082,1085],{},"J’ai écrit ",[944,1083,1084],{"href":851},"un article entier"," sur ce seul point, parce qu’il change la sensation de toute l’interface. Le résumé : le retour visuel part à l’appui, l’action part au relâchement, et la confirmation arrive après.",[25,1087,1089],{"id":1088},"le-clavier","Le clavier",[18,1091,1092,1093,1096,1097,1100],{},"Essayez de parcourir l’écran avec la touche Tab. Souvent, l’ordre de parcours ne suit pas l’ordre visuel, le focus n’est pas visible, et la carte cliquable n’est pas atteignable parce que c’est un ",[33,1094,1095],{},"div"," avec un ",[33,1098,1099],{},"@click",". Rien de tout ça ne se voit sur une capture.",[18,1102,1103,1104,1106,1107,1110],{},"Les corrections sont petites : un vrai ",[33,1105,327],{}," ou un vrai lien, un ",[33,1108,1109],{},":focus-visible"," dessiné, l’ordre du DOM aligné sur l’ordre de lecture. Mais il faut les faire, et il faut y penser.",[25,1112,1114],{"id":1113},"le-texte","Le texte",[18,1116,1117],{},"Les textes générés sont corrects et interchangeables. « Gérer vos projets », « Commencer maintenant », « Une erreur est survenue ». Ils remplissent la case. Un texte qui aide est spécifique : il nomme ce qui va se passer, dans les mots de la personne qui lit.",[18,1119,1120],{},"Je réécris presque tous les libellés d’un écran généré. C’est le poste qui prend le plus de temps après les états, et c’est celui qui se voit le plus.",[25,1122,1124],{"id":1123},"les-écrans-intermédiaires","Les écrans intermédiaires",[18,1126,1127],{},"Entre 375 et 1440 pixels, il y a des largeurs où tout casse : une grille à trois colonnes qui devient illisible à 800, un titre qui déborde à 390, un tableau qui force le défilement horizontal. Les générations sont testées sur un écran, le vôtre, et c’est souvent le seul qui marche.",[18,1129,1130],{},"Je redimensionne la fenêtre lentement, de la plus étroite à la plus large, et je note chaque endroit où quelque chose saute. Ça prend deux minutes et ça évite les retours clients.",[25,1132,1134],{"id":1133},"le-mode-sombre","Le mode sombre",[18,1136,1137],{},"Si le projet a un thème sombre, le code généré oublie une couleur sur trois. Un fond blanc codé en dur dans une carte, une ombre invisible, un texte gris sur gris. La solution est en amont : ne jamais écrire une couleur dans un composant, toujours passer par les variables du thème. Une fois la règle dans le fichier de conventions, l’assistant la suit.",[488,1139,1140],{"type":491},[18,1141,1142],{},"Tout ce qui précède tient en une idée : l’IA produit ce qu’on lui décrit, et personne ne décrit les états vides, le focus clavier ou le texte d’erreur dans un brief. Ce sont les angles morts de la description, pas de l’outil.",[25,1144,1146],{"id":1145},"ma-liste","Ma liste",[18,1148,1149],{},"Avant qu’un écran généré n’aille plus loin, je passe ces points dans l’ordre :",[954,1151,1152,1155,1158,1161,1164,1167],{},[346,1153,1154],{},"Les quatre états existent et disent quoi faire.",[346,1156,1157],{},"Chaque action a un retour à l’appui et une confirmation.",[346,1159,1160],{},"Tout est atteignable au clavier, dans le bon ordre, avec un focus visible.",[346,1162,1163],{},"Chaque libellé nomme ce qui va se passer.",[346,1165,1166],{},"L’écran tient à 390, 768, 1024 et 1440 pixels.",[346,1168,1169],{},"Aucune couleur n’est écrite en dur.",[18,1171,1172],{},"Six points, dix minutes. C’est la différence entre un écran qui fonctionne et un écran qu’on a envie d’utiliser.",{"title":68,"searchDepth":86,"depth":86,"links":1174},[1175,1176,1177,1178,1179,1180,1181],{"id":1035,"depth":86,"text":1036},{"id":1074,"depth":86,"text":1075},{"id":1088,"depth":86,"text":1089},{"id":1113,"depth":86,"text":1114},{"id":1123,"depth":86,"text":1124},{"id":1133,"depth":86,"text":1134},{"id":1145,"depth":86,"text":1146},"2026-10-07","Une IA génère un écran correct en quelques secondes. Les détails qui le rendent agréable, ou simplement utilisable, viennent après. Liste de ce que je vérifie à la main.",{},{"title":1024,"description":1183},"Interface générée par IA : 6 points à vérifier à la main","blog\u002Fce-que-l-ia-ne-voit-pas-dans-une-interface",[1018,1189,1190],"Interface","Accessibilité","slate","h6PnK0QzuO576IsiV6s7VYyIjKFmKVXSJ2Lu4TYMc0M",1793214702904]