标签
HTML寫了checked=”false”還是勾選:分清屬性與目前狀態
模板輸出checked="false",核取方塊卻還是打勾?在原生HTML裡,checked是布林屬性;寫進標籤就表示預設勾選,不會把字串false當成關閉指令。若要預設不勾選,應該省略整個checked屬性。 false不是checked的合法關閉值 先把下面兩個欄位放進一個空白頁,比較初始畫面。第一個是常見的錯誤輸出,第二個才是預設不勾選的寫法。 <!-- 錯誤示範:仍會預設勾選 --> <label> <input type="checkbox" checked="false"> 接收更新通知 </label> <!-- 預設不勾選:不輸出checked --> <label> <input type="checkbox"> 接收更新通知 </label> 布林屬性可只寫名稱,例如checked;也可寫checked=""或checked="checked"。字串true和false都不是合法取值。瀏覽器仍會因為屬性存在而把第一個方塊設為預設勾選,但不要把這種容錯行為當成正確格式。 模板應決定是否輸出整個屬性 檢查模板最後送到瀏覽器的HTML。當設定為關閉時,應得到沒有checked的input,而不是checked="false"或checked="0"。若資料來自資料庫或API,先依資料格式把它轉成真正的布林值,再決定是否輸出checked;別直接把字串塞進屬性。 這裡說的是原生HTML。框架的屬性綁定有自己的語法與轉換規則,不能把原始碼中的綁定表達式直接當成最後的HTML。先看瀏覽器裡實際產生的元素,再查該框架的綁定文件。 頁面載入後,讀checked目前狀態 如果要在JavaScript裡取消勾選,用元素的checked屬性,給它布林值false。以下HTML與後面的程式碼可接在一起測試;script放在表單後面,確保先取得元素。 <form id="demo"> <label> <input id="notice" type="checkbox" checked> 接收更新通知 </label> <button type="reset">重設表單</button> </form> const notice = document.querySelector('#notice') notice.checked = false console.log(notice.checked) // false:目前狀態 console.log(notice.defaultChecked) // true:預設狀態 console.log(notice.hasAttribute('checked')) // true 使用者手動取消勾選時,HTML上的checked也不一定消失。判斷「現在有沒有勾選」應讀notice.checked,不要用getAttribute('checked')或屬性是否存在來代替。 重設後又勾選,先查預設狀態 在上面的表單按「重設表單」,方塊會回到預設勾選。notice.checked=false只改了當下狀態,沒有把HTML的預設值一起改掉。若需求是連重設後也維持預設不勾選,就要另外調整預設狀態;不要只在畫面上取消一次便以為完成。 還要分清checked與value。value是欄位勾選並提交時使用的值,不是打勾開關。一般表單中,未勾選的checkbox不會提交該欄位的名稱與值;後端若需要明確的關閉值,須按自己的資料格式處理,不能期待checked="false"替你送出false。 參考資料 MDN:HTML布林屬性的合法寫法 MDN:checked與defaultChecked MDN:checkbox預設狀態及提交值
HTML必填欄位被跳過:submit()與requestSubmit()送出表單的差別
聯絡表單的信箱明明設了 required,使用者按原本的送出按鈕也會看到提示;換成JavaScript控制的按鈕後,空白資料卻直接送走。先找程式是不是呼叫了 form.submit()。這個方法會略過瀏覽器的欄位驗證,也不會觸發表單的 submit 事件。 如果你要的是「由程式發起一次正常送出」,通常應使用 form.requestSubmit()。它會走驗證與submit事件流程,但仍要核對表單是否設了略過驗證的屬性,以及事件處理器是否取消送出。 兩個方法,差在送出前經過哪些步驟 submit() 直接啟動表單送出,不經互動式欄位驗證,也不派發submit事件。把必填檢查、送出前確認或資料整理全放在submit監聽器裡,這些程式也會被繞過。 requestSubmit() 則會先做正常的限制驗證。若必填信箱空白或格式不符,瀏覽器阻止送出;驗證通過後才觸發submit事件。事件裡若呼叫 event.preventDefault(),正常的表單導向仍會被取消,例如改由你自己的非同步程式處理。 所以「看見submit事件」與「伺服器收到並接受資料」是兩件事。後端仍要檢查欄位、權限及請求是否合法,不能依賴瀏覽器的required保護。 先看表單與指定的送出按鈕 下面是詢問表單的最小結構。信箱欄位有name、type與required;送出按鈕則帶有 intent=inquiry,供後端辨識這次操作。請把action換成自己測試站的接收路徑,別直接拿客戶表單測空白送出。 <form id="inquiry" action="/inquiry" method="post"> <label>信箱 <input name="email" type="email" required> </label> <button id="send" type="submit" name="intent" value="inquiry">送出詢問</button> </form> 程式若要沿用這顆按鈕的送出行為,先取得表單與按鈕,再傳給requestSubmit。這段應放在元素已建立後執行,例如載入於頁尾或使用defer的腳本。 const form = document.querySelector('#inquiry'); const send = document.querySelector('#send'); form.requestSubmit(send); 傳入的必須是這份表單所屬的submit按鈕;一般 type="button" 按鈕不能當這個參數。指定send後,這顆按鈕的name/value會參與送出,submit事件的 event.submitter 也能識別它;按鈕上的formaction或formmethod等覆寫設定同樣需要核對。 空白、合法值與按鈕參數,分三次比較 在自己的練習頁加一個submit監聽器,先只記錄事件,不取消正常送出。開著瀏覽器開發者工具的Console與Network,再做以下比較;示範信箱使用保留測試網域 [email protected]。 form.addEventListener('submit', (event) => { console.log('submit事件', event.submitter); }); 第一次,保持信箱空白,呼叫requestSubmit(send)。 應看見欄位提示,沒有submit事件,也沒有這次正常表單請求。若空白仍送出,先查form上的novalidate與按鈕上的formnovalidate,不要只看required是否存在。 第二次,仍保持空白,呼叫submit()。 在這個沒有其他攔截的普通表單裡,瀏覽器會送出空信箱,submit監聽器不會執行。這正是兩個方法的行為差別;不要拿正式表單做這一步。 第三次,填入合法信箱,呼叫requestSubmit(send)。 此時會經過submit事件,submitter指向send,請求包含按鈕的intent值。接著比較不帶參數的 requestSubmit():它仍會驗證及觸發submit事件,但不會自動挑選那顆send按鈕,該按鈕的name/value也不會因此加入。 改過方法還是不對,查這三個位置 先檢查是否在form上放了 novalidate,或指定按鈕帶著 formnovalidate。這些屬性會影響正常驗證流程,改成requestSubmit也不代表強制把它們取消。 再找submit事件內有沒有 preventDefault()。如果有,檢查後續程式是否真的發出請求、失敗時有沒有恢復按鈕;不要因為沒有頁面導向,就判定requestSubmit無效。 最後核對欄位命名。把某個控制項的name或id設為 submit,可能遮住表單的submit方法,呼叫時出現「submit is not a function」。避免使用會碰到表單API的欄位名,先改成email、message等有實際意義的名稱。 若你的問題是「按鈕放進form就意外送出」,先看 HTML按鈕type的設定;本篇處理的是程式主動送出時的驗證流程,兩個入口要分開查。 參考資料...
HTML欄位移到表單外就沒送出:用form指定所屬表單
重新排版表單後,有些欄位移到側欄或固定操作區,畫面仍看得到,提交資料卻少了一項。先查欄位的所屬表單。普通欄位放在form外,不會只因為靠近某個表單就自動加入;需要跨版型放置時,可以用欄位的form屬性指定表單的唯一id。 這不是把整份網頁都當成一張表單。每個欄位有自己的所屬關係,提交時再按控制項狀態和name整理資料。本文用兩個獨立表單比較FormData,讓你先確認欄位究竟連到哪裡,再回頭調整實際版型。 用form id連接外部欄位 <form id="main"> <input name="inside" value="A"> </form> <input id="outside" name="outside" value="B" form="main"> <button type="submit" form="main">送出</button> outside和送出按鈕雖然在form元素外,仍能透過form="main"連到id為main的表單。這個值是表單id,不是表單name,也不是欄位name。欄位name負責資料鍵名,form屬性則負責所屬表單,兩者不要混淆。 這種寫法可以讓版型把欄位與按鈕分放不同區域,但表單必須存在於同一份文件中,而且id唯一。它不會跨iframe去尋找另一份文件的表單,也不會因為某個遠端頁面上剛好有main就建立關聯。 先查input.form,不要只看位置 const input = document.querySelector('#outside'); console.log(input.form?.id); input的form屬性可查看瀏覽器已判定的所屬表單。如果結果是undefined或null,要查它是否真的在某個form裡、form屬性有沒有對上存在的id。若連到另一個表單,則看是否被明確的form屬性指定了其他目標。 外部欄位即使顯示在表單正下方,也不代表它會跟著提交;反過來,一個控制項也可能因明確指定form而屬於畫面上較遠的表單。重排模板時,DOM父子關係和form owner都要核對,不能只憑CSS位置判斷。 用兩個表單比較實際資料 <form id="main"> <input name="inside" value="A"> </form> <form id="other"></form> <input id="outside" name="outside" value="B"> <input id="linked" name="linked" value="C" form="main"> const main = document.querySelector('#main'); console.log([...new FormData(main).entries()]); 在Chromium154的這組獨立頁面,main的資料起初只有inside=A和linked=C。outside雖然有name和值,仍未屬於任何表單,所以沒有加入。把outside加上form="main"後,資料才多出outside=B。 接著把linked改成form="other",main就不再包含linked;other的FormData則包含linked=C。這是欄位所屬關係改變,不是資料因為空值被忽略。對多表單頁面而言,一個看似無害的form屬性就可能讓資料提交到另一段流程。 fieldset的form不會傳給子欄位 <fieldset form="main"> <input name="nested" value="D"> </fieldset> 這段fieldset在main外,給它form="main"不會讓裡面的input自動繼承所屬表單。在本例中nested的input.form仍為null,main的FormData也沒有nested。若你要讓這個外部input參與main提交,應在input本身設定form="main"。 表單不是靠一個任意外層區塊就能把資料「整包掛上去」。像把section或div加form屬性,也不能當成欄位關聯機制。應使用支援form關聯的控制項,逐一確認實際owner,再用提交資料檢查結果。 如果子欄位本來就在main內,則可以透過所在的form取得關聯;這與外部fieldset不繼承是兩回事。範例故意把fieldset放在form外,讓差異清楚。排查時要保留你實際的DOM結構,不要把欄位臨時移回form裡測通,就以為外部版型也已經修好。 有所屬表單,仍要符合提交條件 正確form owner不保證每個欄位都會出現在FormData。例如沒有name的欄位不會以你期待的鍵名提交,disabled控制項通常也不會加入;核取方塊還要看是否勾選。這些是資料收集的規則,應在所屬關係確認後再逐項排查。 required與FormData也有不同用途。new FormData整理可提交欄位,並不會替你完成整張表單的交互驗證。如果你用JavaScript自定義提交,還要確認使用者真正的送出路徑是否檢查有效性,不要只看到資料物件建立成功就判定所有欄位都合格。 後端更不能因為欄位帶有form屬性就相信它來自正確介面。使用者可以自行組織請求,所以權限、必填、允許值與資料型別仍須在後端驗證。form屬性解決的是瀏覽器裡控制項的歸屬,不能代替安全或業務規則。 避免用巢狀form包住版型 HTML不允許把form當成一般容器反覆巢狀。若為方便排版把一張form塞進另一張,瀏覽器解析出的結構可能與原始碼想像不同,控制項歸屬也容易混亂。把佈局區塊改用合適容器,表單本身保持清楚獨立,再用form屬性連接外部控制項。 元件式版型尤其要看最終DOM。父元件可能已有form,子元件又包了一張,開發者只看各自檔案時不易發現。用瀏覽器檢查實際元素樹與input.form,能看出組合後究竟是哪個表單負責欄位。 不要用JavaScript提交時遍歷全頁所有input來補漏。多表單頁面可能混入搜尋、登入與聯絡欄位,全部收集反而會把無關資料送出去。先修正每份表單的關聯,再使用那張表單的資料,範圍會比較清楚。 重排後核對兩份資料各自的內容 外部送出按鈕也要一起核對。按鈕的form指到哪一張表單,點擊時就會走那張表單的提交流程;如果版型複製後只改欄位卻漏改按鈕,畫面可能顯示一份資料,實際送出的卻是另一份。使用明確的type="submit"與form值,讓後來維護的人看得懂它的作用。 若同一份表單有「儲存草稿」與「正式提交」兩個按鈕,還要看按鈕是否使用不同的提交覆寫設定。這不是把欄位掛到表單後就自然解決的問題。先維持你原本的功能約定,再分別點擊兩個入口觀察請求;不要為了版型方便,把原本不同的操作合成一個含糊的送出事件。 對多步驟表單而言,藏起來的欄位仍可能屬於同一張表單。你應先決定每一步哪些資料可以保留、哪些要停用或重設,再核對提交結果。form owner只說明歸屬,不會按畫面是否可見替你決定哪些資料應該保留。把這些規則留在表單邏輯裡,後續改版較不容易誤刪使用者已填的內容。...
HTML email輸入兩個地址變無效:multiple怎麼判斷
通知欄位要填兩個Email,第一個地址正常,一加第二個就被瀏覽器擋住,先看input是否有multiple。一般type="email"只接受一個地址;加上multiple後,可接受以逗號分隔的地址清單。分號不是這個原生欄位的分隔符號,即使某些郵件軟體使用分號,也不能直接套用。 這個設定只處理瀏覽器可判斷的格式,不證明地址存在、能收信或由填寫者持有。網站要真正把通知寄到這些地址,還需要後端解析、權限與寄送流程。先把輸入規則說清楚,訪客才不會把原本可用的兩個地址反覆修改成不同格式。 一個地址與多個地址的欄位不同 <label for="emails">通知地址</label> <input id="emails" name="emails" type="email" multiple> <p>多個地址請以逗號分隔。</p> 這段把通知地址設成選填。如果至少要填一個地址,再加required。不要只在提示文字寫「可以填多個」,卻保留沒有multiple的input;瀏覽器不知道你的說明文字,仍會按單一地址的規則判斷。 例如[email protected]與[email protected]這兩個合成地址,可組成[email protected],[email protected]。寫成[email protected];[email protected]則不能用同樣方式通過。畫面提示最好直接展示分隔方式,避免訪客從別的系統貼上一整串時才發現不符合。 multiple也不是把資料自動提交成兩個相同name的欄位。這個input仍有自己的value,內容是一段地址清單。後端不能以為已拿到一個陣列,直接迴圈寄送;應按格式解析,再對每個項目進行你的資料驗證。 用typeMismatch核對格式問題 const input = document.querySelector('#emails'); console.log(input.value); console.log(input.validity.typeMismatch); console.log(input.validity.valueMissing); console.log(input.checkValidity()); 在Chromium154的獨立頁面裡,沒有multiple時填兩個逗號分隔的地址,typeMismatch為true,欄位無效。加上multiple後,同一段字串的typeMismatch變成false,欄位有效。改用分號時,typeMismatch又變為true。 兩個地址之間的逗號後即使有一般空白,瀏覽器也會依email多值規則處理。在這組案例中,[email protected], [email protected]的value整理為沒有分隔後空白的形式,並通過驗證。這不表示任意奇怪字元都會被清理,也不要依賴前端去修正一份未知格式的地址名單。 檢查時可以在網路面板看真正送出的值,再和後端接收內容比較。若程式又對value做了替換或合併,問題可能出在你的轉換流程,而不是瀏覽器原生email驗證。測試用合成地址即可,不需要把真實客戶信箱放到公開截圖裡。 空欄位由required決定 沒有required時,空字串不會單靠type=email就被判成錯誤。加了required後,空值在本例的valueMissing為true,checkValidity為false。typeMismatch和valueMissing回答不同問題:前者是格式不符合,後者是必填值缺席。 因此「格式沒錯」不能代替「有填地址」。如果後端要求至少一位收件人,應明確驗證數量,不要只檢查所有已提供的項目是否看似Email,讓空清單也走到寄送流程。相反,選填通知功能則應允許使用者不提供地址,而不是暗中套入一個預設收件人。 加pattern時還有自己的限制要核對,不能假設它與multiple組合就自動表達所有公司信箱政策。像只允許某個網域、限制收件人數、禁止重複地址,這些是功能規則,應在後端有明確檢查與錯誤訊息。 後端要按每個地址處理 如果你的契約使用逗號清單,後端可以先拆分,再清理各項外圍空白,檢查每個值與總數。收到只有逗號、某項空白、任一地址格式錯誤時,應按既定規則拒絕或提示修正,不能只取第一項便宣稱其餘輸入已經處理。 收件人上限也應與介面一致。如果功能最多允許三個地址,提示應說明上限,後端則在寄送前真正拒絕超過的輸入。不要讓前端能填二十個,後端悄悄只寄前三個;使用者看不到被忽略的地址,容易把沒有收到信誤認為系統故障。 對重複地址要先決定是否合併。這裡的示範沒有提供一套通用Email正規化規則,因為地址的大小寫與服務商特殊別名可能有自己的限制。先依你的寄送服務與資料契約處理,不要為了去重把不同地址擅自改成同一個。 格式通過,不等於驗證收件人 瀏覽器不能只憑字串確認信箱是否存在。即使後端格式驗證也通過,仍可能是拼錯、停用或無法接收的地址。需要確認使用者持有信箱時,應設計適當的確認信流程;需要發送業務通知時,則要處理寄送回應與失敗紀錄。 新增多地址還可能改變通知範圍。原本只通知登入會員,若改成讓人任填多個地址,就應確認哪些資訊能寄出、誰有權新增收件人。不能因為這是一個HTML屬性修改,就忽略後端原本的資料權限。 若你不需要多收件人,保留單地址欄位反而清楚。multiple適合真的會按清單處理的功能,不宜為了讓一段錯誤輸入「看起來通過」就加上去。介面接受的內容應和後端能處理的內容一致。 提示要指向輸入方式 當格式錯誤時,提供「多個地址請用逗號分隔」比只說「資料錯誤」有用。若能指出某一項不符合,也要保留其餘已填內容,讓使用者只修正那一項。不要每次送出失敗就清空整個通知名單,逼人從頭貼上。 也可以考慮把每個地址做成獨立可編輯欄位,但那是另一種資料結構與操作設計。這篇先保留原生email多值欄位,幫你確認為何兩個地址會被擋。若之後改成多欄位或標籤式輸入,應重新核對鍵盤操作、刪除和提交資料,不沿用這次單一input的結論。 用六種輸入確認修改 若地址來自貼上,測試時也加入前後一般空白,確認畫面提示沒有誤導。不要把其他不可見字元或任意分隔符一律移除;清理過度可能把兩個原本分開的地址黏成一項。遇到不支援格式時,明確要求修改,比悄悄猜測更容易保住使用者要填的內容。 至少保存單地址、兩個逗號地址、分號地址、含逗號後空白、空選填與空必填的測試。這六種輸入能幫你分辨multiple、格式和required的責任。再把同一組合成資料送到測試後端,確認它真的逐項解析、驗證與回報。 最後核對錯誤提示和功能文件:訪客看到的是逗號規則,後端也應使用相同規則。不要讓HTML驗證、前端清理及後端寄送各自接受不同格式。維持同一套輸入約定,比不斷新增例外字元更容易讓通知功能穩定。 參考資料 HTML email HTML multiple typeMismatch valueMissing HTML標準:email
HTML下拉選單選了提示仍能送出:required與空值選項
下拉選單第一項顯示「請選擇」,使用者沒有換選項卻仍能送出,先看那一項的value。required判斷的是選擇值,不知道哪段文字只是提示。如果「請選擇」的value是choose,瀏覽器會把它當成非空值;要讓普通單選select的提示選項代表未選擇,應給它空字串value。 這類問題常見於聯絡表單的需求類型、預約項目或商品選項。提示文字讓人以為還沒選,但提交資料已經帶了一個值,前後端就容易出現不同判斷。本文比較三種選擇狀態,再把原生驗證與提交資料的用途分開。 先建立帶空值提示的普通單選 <form id="request"> <label for="plan">需求</label> <select id="plan" name="plan" required> <option value="">請選擇</option> <option value="site">建站</option> </select> <button type="submit">送出</button> </form> 這裡select沒有multiple,顯示方式也是一般單選。第一個option直接放在select下,value明確設成空字串。保留提示選項時,原生驗證會認為缺值;選到建站後,提交值則是site,不是畫面上的「建站」。 提示文字和資料值應分清。文字供訪客讀,value供程式判斷;可以修改文字讓它更自然,但不要隨意改掉後端需要的代碼。若建站對應site、維護對應maintenance,就讓前後端都使用同一套約定,而不是從顯示文字猜用途。 不寫value,提示文字可能就成了值 option沒有value屬性時,瀏覽器會從選項文字取得值。因此<option>請選擇</option>看起來像提示,資料卻不是空的。只替select加required仍不能達到「必須換一個正式項目」的效果。 同樣地,<option value="choose">請選擇</option>也有非空值。choose這個字對瀏覽器沒有特殊含義,不會自動被當成未完成。使用disabled或灰色樣式也不是一套通用驗證規則,應先把提示選項的資料值設對。 如果你使用多選、較大的size顯示列表,或把提示放進optgroup,原生select的提示選項規則就不能直接照普通單選理解。先確認你的元素結構,再依所需操作設計。本文的空提示例子限定為一般單選,不宣稱所有選單形式都能用同一個第一項解決。 用瀏覽器看valueMissing 開啟這個獨立頁面後,可以在開發者工具查看以下幾個值。checkValidity檢查元素是否符合當前原生限制;valueMissing則讓你知道是否是required缺值,而不是其他限制。 const select = document.querySelector('#plan'); console.log(select.value); console.log(select.validity.valueMissing); console.log(select.checkValidity()); 在Chromium154實際比較,空提示的value是空字串,valueMissing為true、checkValidity為false。把提示value改成choose後,仍顯示同樣的「請選擇」,但valueMissing變為false、checkValidity變為true。選到site時則得到site,驗證同樣通過。 這個結果說明,畫面上的提示字樣本身不控制有效性。若功能要求使用者只能從你提供的正式代碼中選擇,後端還要驗證代碼是否在允許清單;不能因為瀏覽器說有效,就相信任何送來的非空字串。 FormData會整理資料,但不代替驗證 const form = document.querySelector('#request'); const data = new FormData(form); console.log([...data.entries()]); 保留可提交的空提示時,這段程式仍能產生plan對應空字串的資料。FormData建立資料物件,不會因為select無效就自動拋出「表單沒有填好」的錯誤。若你用fetch自行送出,應在適當的提交流程確認有效性,再決定是否建立與送出資料。 使用原生submit按鈕時,正常的交互驗證會檢查表單限制;但novalidate等設定或不同提交方式,可能略過這段。排查時要看真正的送出路徑,不要只用new FormData得到幾列結果,就判定表單驗證已經完成。 disabled提示不是唯一解法 有些版型會把提示option設為disabled,避免使用者選到正式項目後又回到提示。這可以是介面選擇,但disabled選項與一般選項在提交資料上也有差別,所以後端仍要接受缺席或空值並給出清楚錯誤。 如果你的需求允許使用者取消選擇,禁止回到提示可能反而不方便。先決定訪客應否能清除,再選擇是否停用提示。不要把「提示不能點」當成萬用做法,尤其在編輯舊資料、重設表單或動態更新選項時,更容易出現沒有合適選擇的狀態。 selected也與disabled不同。selected描述預設選擇,disabled描述能否選取或提交;它們都不能代替空值與required的設計。建立表單時可以讓空提示為預設,再測使用者選正式值、清除與重設後的結果。 動態選項更新後,重新看實際值 若縣市、方案或分類選單由JavaScript重新產生,先前選到的value可能不再存在。更新選項後,應重新確認select.value與驗證狀態,讓畫面文字、提交資料及提示一致。不能只保留前端變數裡的舊代碼,就以為欄位仍維持同一個有效選擇。 使用者看不到的錯誤也要給出口。如果選項載入失敗,直接留一個required空選單而沒有說明,訪客會不知道如何繼續。可以提供重新載入或其他聯絡方式,但不要硬塞一個非空預設值來讓驗證通過,否則收到的需求資料可能是你替訪客猜的。 上線前用三份資料核對 如果表單使用JavaScript處理submit事件,應確認程式在錯誤時保留使用者的其他輸入,而不是整張表單重設。讓訪客重新選需求即可,不必要求他再輸入姓名和訊息。錯誤提示也應靠近選單,說明要選正式項目,不能只在頁面頂部顯示一個沒有對應欄位的「提交失敗」。 新增選項時,維護者應同時更新後端允許清單。前端出現新代碼但後端不認得,訪客就會遇到「選得出卻送不了」;後端仍接受已下架代碼,則可能收到不可處理的需求。這是資料約定的維護工作,不能靠required解決。 若你暫時沒有可選項目,應向訪客說明狀態,避免顯示一個永遠無法通過的必填欄位。比如服務尚未開放時,提供可用的查詢或聯絡方式;資料載入中則顯示相應提示。這些訊息應按實際功能設計,不要用假的預設方案讓表單勉強送出。 最後也看回填舊資料的情況。如果後端存的是site,選單應正確選回site;若舊代碼已移除,應清楚呈現需要重新選擇,而不是偷偷替換成第一個正式項目。保持訪客看到的選擇與真正提交的值一致,才不會讓一個小欄位影響整份需求內容。 至少測提示未改、正式site、任意不支援代碼三種輸入。第一種應要求補選,第二種應正常處理,第三種應由後端拒絕。再核對表單重設後回到哪個選項,以及錯誤提示能否讓人知道要修改哪一欄。 這些測試不需要操作真實客戶訂單。用獨立測試頁與合成需求即可先確認原生行為,再到你自己的測試站核對實際提交路徑。修好空提示是一個欄位的驗證改善,後端的權限、價格或可用方案仍要按各自規則處理。 參考資料 HTML select HTML option valueMissing FormData HTML標準:select
HTML點標籤卻沒選中欄位:for要對上唯一id
表單的文字放在欄位旁邊,點它卻沒有反應,常見原因是label沒有正確連到欄位。使用for時,它的值要與輸入欄位的id完全一致,而且這個id在整份文件中必須唯一。畫面位置接近、欄位的name相同,都不能代替這個關聯。 這種問題特別容易在複製聯絡表單、彈出視窗或會員資料卡後出現。原本的一組能用,第二組卻點到第一組欄位。先查標籤實際連到哪個控制項,再修id,比替每段文字加一個點擊事件更直接,也能保留原生表單的操作方式。 一組最小可用的標籤與欄位 <label for="contact-name">姓名</label> <input id="contact-name" name="name" type="text"> 把這段存成獨立HTML頁,點擊「姓名」。瀏覽器會把焦點放進輸入框,讓你開始打字。for與id負責建立關聯;name則是提交資料時使用的欄位名稱。它們可以不同,不需要為了連接標籤而把name也改成id。 假如for寫成contact-name,輸入框id卻是contact_name,一個用減號、一個用底線,便無法關聯。這裡比對的是識別字,不會依文字意思自動找「最像的欄位」。若輸入框只有name而沒有id,明確的for關聯也找不到目標。 在瀏覽器開發者工具選中label,檢查它的for值,再搜尋對應id是否存在。也可以查看label的control屬性,確認它實際關聯到哪個元素。這比只看HTML原始碼裡似乎有一個相近名稱更可靠,尤其模板會在載入後新增內容時。 實際點三組標籤,看看差在哪裡 以下片段展示正確、不匹配及重複id三種情況。重複id刻意作為錯誤範例,不能用在正式頁面。你可以點擊三段文字,觀察焦點與核取方塊的勾選位置。 <label for="contact-name">姓名</label> <input id="contact-name" name="name"> <label for="wrong-name">錯誤標籤</label> <input id="actual-name"> <label for="notify">第二份通知</label> <input type="checkbox" id="notify"> <input type="checkbox" id="notify"> 在Chromium154的這組頁面裡,點姓名後焦點到了contact-name;點錯誤標籤沒有找到對應控制項;點第二份通知時,第一個notify核取方塊被勾選,第二個仍未勾選。標籤不會猜測你想操作第二個,也不會依最近的畫面位置選目標。 同一份文件若有多個相同id,label會依第一個符合id的元素判斷關聯。如果那個元素不是可標記控制項,後面即使還有同id的input,也不能拿這種重複結構期待它自動補救。id重複是要修正的HTML問題,不是可利用的選擇策略。 複製模板時,id與for要一起改 假設桌面版與彈窗各有一份姓名欄位,可以用contact-name-main與contact-name-dialog這類清楚的id。每個label的for跟著改成對應值,name則仍按後端需要保留為name。只改input的id而漏掉label,是另一種常見的半套修正。 如果表單是在迴圈裡建立,應讓每筆資料產生穩定且唯一的id,再把同一個值傳給label。不要每次重繪隨機換識別字,導致其他描述關聯也跟著失效。框架有產生id的工具時,可以按它的規則使用,但仍要確認輸出到DOM後沒有重複。 檢查範圍要涵蓋整頁:藏起來的表單、尚未開啟的彈窗、手機與桌面兩份版型,都可能留在同一份文件中。看不到不代表id不會參與比對。因此不能只在目前可見區域搜尋,或以為用CSS隱藏後就可以重用id。 也可以把欄位放進label裡 <label> 接收更新通知 <input type="checkbox" name="notify" value="yes"> </label> 這是隱含關聯,input直接在label裡,不必使用for與id才能建立這個標記。它適合某些簡單核取方塊排版;若你的設計要把標籤和欄位分成不同欄位,明確的for/id則比較容易安排。選擇方式時也應考慮輔助工具的相容性,而不是只看少幾個字。 一個label不要包進多個需要操作的欄位,也不要用它當整張卡片的任意點擊容器。標籤要有清楚的控制項對象。如果另外有條款連結或說明按鈕,最好放在合適的位置,避免使用者原本要勾選卻打開連結,或要開連結卻切換欄位。 可見標籤與placeholder用途不同 placeholder可以提供填寫例子,但輸入後會消失,不適合單獨承擔欄位名稱。修for時也順便看看頁面是否有持續可見的標籤,讓使用者在修改已填資料時仍知道每格是什麼。不要只為了排版乾淨,把所有label都刪掉。 對輸入框而言,正確關聯有助於工具取得欄位名稱,也增加可點擊的範圍。不過單靠這組點擊測試,不能推論所有瀏覽器與螢幕閱讀器組合的表現。正式功能應再使用鍵盤走過表單,確認每個欄位有清楚名稱、焦點看得見,錯誤提示也能對應到適當欄位。 改完後先驗三件事 檢查標籤時可以再看label.control是不是你預期的欄位。如果它是null,先回到for、id與可標記元素種類;如果它指向另一個欄位,則優先查重複id和模板複製。這個屬性可用來排查關聯,不需要為了修表單在網站裡長期插入一段自動改寫所有標籤的程式。 不要把label和其他描述關聯混用。label應讓人知道欄位要填什麼,提示可以說明格式或選填規則,錯誤則應指出目前要改的內容。改了id後,若其他描述也引用同一個識別字,應一起核對,避免欄位名稱修好,補充提示卻指向了舊元素。 有些版型把「姓名」做成普通span,看起來與label相同,但span不會自動把點擊交給輸入框。若這段文字就是欄位標籤,應使用合適元素建立關聯;若是區塊標題,則保留它的用途,不要把整個標題區都改成label。 核取方塊尤其需要留意可點擊範圍。小方框本身不容易點中,正確標籤能讓文字也參與操作;但外層再套一個會跳頁的卡片連結,可能讓點擊結果混亂。先保留簡單的一組標籤和欄位確認行為,再逐步加回外層版型,較容易找到是哪個元素影響操作。 先點可見文字,確認焦點或勾選落在正確欄位;再用Tab走過表單,確認沒有為了修標籤而加入多餘的焦點停留;最後提交合成資料,核對欄位名稱仍符合後端約定。for/id修好了,不表示name與value也一定正確。 若點標籤還是沒有反應,再看是不是有覆蓋元素攔住點擊,或控制項本身被停用。這些問題與關聯錯誤不同,應分別查證。不要一律加JavaScript click來繞過,否則容易把模板裡真正的錯誤留在頁面,之後複製到其他表單時又再次出現。 參考資料 HTML label HTML id HTML checkbox label control HTML標準:label
HTML數字欄位輸入1.5卻無效:step怎麼設定
數量欄位用了input type=number,使用者輸入1.5,瀏覽器卻說不是有效值。既然是數字欄位,為什麼小數不行?常見原因是step:number欄位預設步階是1,值必須落在以步階基準開始的允許位置,而不是只要能解析為數字就有效。 需要半份、長度或小數單價時,先確定允許的精度與起點,再設定step。step不是顯示格式,也不是四捨五入工具。它參與原生約束驗證;真正的資料規則仍要在後端確認。 預設步階1,先看stepMismatch 以下input沒有min,也沒有初始value屬性,因此步階基準是0。用JavaScript設定目前值為1.5,再讀取有效狀態,可以把問題和「欄位是空的」或「超過最大值」分開。 <input id="quantity" type="number" name="quantity"> const quantity = document.querySelector('#quantity'); quantity.value = '1.5'; console.log(quantity.validity.stepMismatch); // true console.log(quantity.checkValidity()); // false quantity.step = '0.5'; console.log(quantity.validity.stepMismatch); // false console.log(quantity.checkValidity()); // true step改成0.5後,從0開始的0、0.5、1、1.5都符合這個步階。沒有max或其他限制,不代表只能在這幾個值裡選;它仍沿著相同間距延伸。若業務只能選固定幾種數量,可能使用選單或按鈕更清楚,不必把固定選項偽裝成自由數字輸入。 checkValidity會檢查其他適用的約束,不只step。看到false時,進一步看stepMismatch、rangeUnderflow、rangeOverflow等資訊,才知道是哪個條件不成立。瀏覽器顯示的驗證訊息會依語言與實作不同,不要只比對某一句錯誤文字。 min也會改變步階起點 步階是從基準起算。對number欄位,基準依序取有效的min、有效的value內容屬性,否則是0。這也是為什麼你把min加上去後,原本某個整數可能反而不符合step。 <input id="length" type="number" min="0.2" step="1"> const length = document.querySelector('#length'); length.value = '1.2'; console.log(length.validity.stepMismatch); // false length.value = '1'; console.log(length.validity.stepMismatch); // true min=0.2、step=1的允許位置是0.2、1.2、2.2等,並不是所有大於0.2的數字都可以。min限制下界,同時也提供步階起點;兩種作用需要一起考慮。若你的規則是「至少0.2,任何小數都可以」,應另外考慮step=any,而非保留預設1。 HTML裡的value屬性和使用者目前輸入的值也要分清楚。沒有min時,初始value內容屬性可能提供步階基準;之後單純改input.value,並不等於把那個HTML內容屬性一起改掉。排查時同時核對getAttribute('value')與目前value,避免只看畫面上的字就推斷起點。 step=any不檢查步階,但其他規則仍在 若業務允許不限固定間距的數值,可以用step=any停用步階不匹配的檢查。min、max、required及數字解析等條件仍各自適用。any不是「所有輸入都有效」,也不會讓文字變成數字。 <input type="number" name="length" min="0.2" max="10" step="any" required> 這個欄位可接受範圍內的小數,不必對齊固定步階;空值仍因required無效,超過10仍超出範圍。若只是想接受兩位小數,可以先評估step=0.01是否符合業務步階,但它不強制畫面永遠顯示兩位,也不會自動把三位小數四捨五入。 例如1.5與1.50的數值相同,顯示要保留尾端零則是格式呈現的需求。數字輸入、金額顯示與後端儲存可以採不同處理方式,不必要求一個step屬性完成全部工作。貨幣資料的計算與儲存也要按系統的精度規則決定,不能只因瀏覽器通過就當成計算一定精確。 把畫面提示和真正規則對齊 如果HTML一開始寫了value=0.5,且沒有min,預設步階1會從0.5起算。此時1.5可能有效,反而1不符合步階。這與本文第一個沒有value內容屬性的input不同,並不矛盾。複製別人的表單時,若只改目前值而保留原本的value屬性,可能把原作者的步階基準也一併帶過來。 需要明確起點時,使用符合業務的min通常更容易讀懂。像允許0.5份、1份、1.5份,可以設min=0.5、step=0.5;若數量至少1份但之後可半份增加,就改成min=1、step=0.5。兩個設定都接受某些小數,最低允許值卻不同。不要只為了讓某個測試數字過關而挑一個任意min。 另外,電話、郵遞區號或商品編號即使只含數字,也不一定適合type=number。它們通常是識別文字,而不是可以加減步階的數值;前置零可能有意義。遇到這類欄位應按資料用途選擇輸入類型,不能用step=any來補救所有「看起來是數字」的欄位。 資料回填也值得測一次。後端傳回字串1.50、前端設定目前value後,畫面可能呈現不同格式,但有效狀態仍要看數值與約束。若驗證失敗,保留使用者輸入並指出允許的步階,比默默改成另一個數字更清楚。需要自動調整數值時,應先讓使用者理解調整規則,再確認送出的值就是畫面最後顯示的值。 欄位旁可以直接說明「每次以0.5為單位」或「長度介於0.2至10」。比起送出後才看到瀏覽器提示,使用者在輸入前就知道允許範圍會更容易填寫。若有單位,例如公尺或份數,也應放在可理解的位置,避免1.5到底代表什麼都不清楚。 當欄位有錯誤時,不要為了讓送出按鈕能按就把所有約束移除。先確認業務到底允許1.5,還是只能輸入整份;前者要修step,後者要修提示與選擇方式。技術欄位設定應反映真正規則,不能由錯誤訊息反過來決定產品能接受什麼。 也要注意空值的處理。沒有required時,空的number欄位通常不會因step而無效;但valueAsNumber可能是NaN。需要區分未填和零,不能直接用真假判斷把0當成沒有填。範例只檢查步階,不代表已完成所有輸入資料的解析與必填檢查。 用幾個邊界值驗證,再檢查後端 number欄位不支援pattern約束,不能加一段正規表示式就要求固定小數格式。若業務需要的其實是某種文字格式,應重新評估欄位類型與解析流程;若需要的是數值步階,則使用step及範圍約束,再由後端核對同一套規則。 本機Chromium案例驗證了預設step下1.5無效、step=0.5下1.5有效,以及min=0.2、step=1時1.2有效而1無效。案例讀取的是原生validity狀態,不是憑欄位外觀推測,也沒有對正式網站提交資料。...
HTML表單欄位沒送出?分清disabled與readonly
報價表上的客戶編號明明看得到,送到後端卻沒有這個欄位。把驗證規則改了幾次,仍然收到「編號必填」。這時先看表單怎麼組成送出的資料:欄位若設成 disabled,瀏覽器會把它排除;畫面顯示的值,不一定在請求裡。 若你的意思是「讓人看得到,不能直接修改,但仍要隨表單送出」,支援唯讀的文字欄位可以用 readonly。兩個屬性外觀可能相近,處理資料的方式卻不同。先確認請求內容,才知道該修畫面、送出程式,還是後端。 先用一張小表單看出差別 以下例子有四個文字欄位:一般欄位、停用欄位、唯讀欄位,以及沒有名稱的欄位。把它放在本機 HTML 檔,用瀏覽器開啟,再從開發者工具的 Console 執行後面的 JavaScript。這個例子只建立資料,不會傳到伺服器。 <form id="quote"> <input name="customer" value="Ada"> <input name="account" value="A17" disabled> <input name="reference" value="R42" readonly> <input value="visible-but-unnamed"> <input type="hidden" name="plan" value="basic"> </form> const form = document.querySelector('#quote'); const data = new FormData(form); console.log([...data.entries()]); 結果包含 customer= Ada、reference= R42 與 plan= basic,沒有 account,也沒有最後那個未命名文字欄位。停用欄位即使有值、有名稱,仍被略過;唯讀文字欄位則照常加入。沒有 name 的控制項沒有送出鍵名,也不能靠 id 補上。 這裡的空格只是文字展示方式,實際值分別是 Ada、R42、basic。不要用肉眼看輸入框是否有字來判斷資料是否送出,直接查看 FormData 的 entries,或在 Network 面板查看真正的請求,會更可靠。 disabled表示暫時不能使用,不只是不能修改 停用的原生表單控制項通常無法取得焦點,也不參與表單約束驗證。例如停用且帶有 required 的欄位,不會因為空值而擋住表單。這符合「目前不適用這個選項」的意思,卻不適合拿來鎖住仍需要送出的訂單編號。 停用也可能來自外層的 fieldset disabled。如果單看 input 沒有發現屬性,往上檢查父層;停用 fieldset 內的控制項通常一起受影響,其第一個 legend 子元素內的控制項有規格例外。不要只靠 CSS 灰色判斷,也不要把 disabled="false" 當成啟用:這是布林屬性,只要屬性存在,就代表停用。要恢復使用,移除屬性或把 DOM 的 disabled...
HTML日期欄位選完還是無效:核min、max與日期格式
日期欄位明明選好了,按「繼續」卻停在原頁。先把欄位的實際值、min、max 和 step 並排看。可見的日期格式、可接受的範圍,以及間隔規則是不同條件,不只看日曆裡有沒有選中一天。 原生 input type="date" 能提供日期輸入與瀏覽器驗證,但網站仍需要自己的服務端規則。這篇先找出欄位為何無效,不把前端通過當作預約已成立或資料已保存。 畫面上的日期與欄位值分開看 瀏覽器可依地區設定顯示不同日期順序,欄位的 value 則使用 yyyy-mm-dd。例如畫面可能顯示 05/10/2026,程式讀到的是 2026-10-05。不要把本機顯示的斜線格式直接寫進 min 或 max。 <input type="date" name="booking_date" min="2026-10-04" max="2026-10-10" required> 範圍值要是有效的日期字串,最早日期也不能晚於最晚日期。錯誤格式可能使對應的範圍限制無效;不要看到日曆還能開啟,就認定屬性已經正確工作。 也核對真正渲染出的屬性。模板可能按目前日期動態設定範圍,或由腳本在載入後改值。設定檔寫的是這個月,輸入框最後拿到另一組日期,仍要依實際 DOM 找出產生差異的位置。 min 與 max 只處理上下限 這份示範欄位最早是 2026-10-04,最晚是 2026-10-10。前後兩個邊界本身可以接受;10 月 3 日早於 min,10 月 11 日晚於 max,則不符合這個範圍。 原生日曆通常能以可選日期協助使用者理解限制,但外觀和操作方式由瀏覽器提供。不要依賴某一種灰色日格,當作所有環境都會有相同提示。把可用範圍寫在欄位旁,訪客即使直接用鍵盤輸入,也能知道需要選哪段日期。 如果產品只允許未來預約,先定義「今天」依哪個時區計算,再由適合的位置產生 min。不要在訪客本機午夜與伺服器日期不同時,讓兩邊各自採用矛盾的界線。這篇使用固定合成日期來隔離範圍問題,不會隨當天時間改動。 範圍裡的日期,仍可能不符合 step 示範另外設定 step="2",表示以指定的基準每隔兩天為有效日期。這裡已有 min,因此以 2026-10-04 為起點:4、6、8、10 日符合;5 日雖然在上下限之間,仍不符合間隔。 <input id="booking-date" name="booking_date" type="date" min="2026-10-04" max="2026-10-10" step="2" required> 日期欄位的 step 單位是天,預設為 1。沒有 min 時,step 基準還可能由 HTML 初始 value 屬性或預設基準決定,不能把「2」只理解成偶數日。若業務是每週特定星期開放,要先確定基準;若是節假日或不規則排程,單靠固定間隔並不足夠。 看本例的瀏覽器判斷 在自建桌機頁面中,我們依序測試最早日期之前、最早日期、間隔不符、最晚日期,以及最晚日期之後。讀取原生 validity:3 日有 rangeUnderflow,11 日有...
HTML下載連結沒下載:同源限制與檔案回應怎麼分
連結加了 download,點下去卻打開文字或圖片,沒有開始下載。先確認連結目標是否與頁面同源,再看最後一個 HTTP 回應。HTML 屬性提供下載用途,伺服器回應提供檔案與處理資訊,兩者不能互相代替。 這篇處理「點連結之後怎麼處理檔案」,不處理中文下載名稱的 UTF-8 編碼。連結能觸發下載,也還需要核對取得的是正確檔案,而不是登入頁或錯誤內容。 先比較頁面和檔案的來源 來源由協定、主機和連接埠組成。頁面在 https://example.com,檔案在 https://files.example.com,就是不同來源;同一主機換了連接埠,也不同。不要只看網址裡都有 example,就判定同源。 MDN 說明 download 適用於同源 URL,以及 blob、data 來源。它不能當成通用的「強制任何外站下載」指令。跨來源檔案能否下載,要看檔案端的回應與瀏覽器行為;網站也必須保留原有授權與存取規則。 <a href="/files/guide.txt" download="guide.txt"> 下載檢查文件 </a> 這個相對路徑只有在實際解析到同源檔案時,才符合這種用途。若頁面使用另一個 base URL、連結被腳本改寫,或請求重新導向到資源主機,仍要核對瀏覽器最後到達的地址。 看完整請求,不只看連結的副檔名 開啟開發者工具的 Network,保留記錄後點連結,查看狀態碼、最終網址、Content-Type 與 Content-Disposition。檔名結尾是 zip,回應仍可能是 HTML 登入頁;文字檔回 200,也可能依回應設定直接在分頁中顯示。 若真正取得檔案的回應使用 Content-Disposition: attachment,它表達附件處理用途。同源連結的 download 和伺服器附件回應,是可以分別提供下載用途的方式,不代表任何一種都能控制訪客的儲存位置或所有瀏覽器設定。 Content-Type: text/plain; charset=utf-8 Content-Disposition: attachment; filename="guide.txt" 這裡只用簡單 ASCII 名稱,目的是看下載行為。若檔案能存下來、只有中文名稱亂碼,另查 Content-Disposition 的檔名參數與編碼;不必為了名稱問題修改跨來源規則。 本例同一份內容,三個入口 我們建立兩個只提供合成文件的本機 HTTP 來源,分別使用 8921 和 8922 連接埠。連結頁放在 8921,三個檔案入口回傳相同文字位元組。先固定內容,再觀察來源和附件回應的差異。 第一個連結指向同源的文字檔,帶 download 屬性。在本例 Chromium 中,點擊產生下載事件,取得建議名稱 local-guide.txt。第二個指向 8922 的文字檔,也帶 download,但回應沒有附件標頭;點擊後瀏覽器導向那個地址,直接顯示文字。 第三個仍指向 8922,但檔案回應加入附件標頭。這次 Chromium 產生下載事件,採用回應中的 server-guide.txt。保存第一與第三份文件後核對雜湊,內容一致,差別在連結來源和回應用途。 同源 + download:本例產生下載 跨來源...
HTML按鈕放進表單就跳頁:先設定正確type
聯絡表單裡新增一個「查看說明」按鈕,點下去卻重新載入頁面,甚至把還沒填好的資料送出去。先看按鈕的 type,再查腳本。一般與表單關聯的 button,如果沒有明確指定用途,可能被當成提交按鈕。 同一個畫面中,「查看說明」只需要操作介面,「送出需求」才需要提交。把用途寫進 HTML,通常比在每個 click 事件裡另外阻止送出更容易維護。 先確認這個按鈕屬於哪個表單 在開發者工具選中按鈕,看它是否位於 form 裡,以及有沒有 form 屬性指向另一份表單。按鈕不在表單標籤之內,也可能透過這個屬性成為表單的控制項,不能只看畫面位置判斷。 接著核對 type。這篇以沒有其他命令用途的普通表單按鈕為例:未指定 type 時,在瀏覽器中讀到的按鈕類型是 submit。原始 HTML 沒有這個字,不代表按鈕沒有提交行為。 <form action="/contact" method="post"> <button>查看說明</button> </form> 上面的文字叫「查看說明」,瀏覽器仍按按鈕類型處理。CSS 把它畫成問號或小圖示,也不會改變用途。先查看輸出的實際 DOM,因為模板元件可能在渲染時加入或移除屬性。 介面動作用 button,提交用 submit 展開說明、切換分類、增加欄位或預覽內容,通常應使用 type="button"。它不會因這個原生按鈕行為提交表單,但仍需要自己的腳本處理要做的事。真正送出則明確寫 type="submit"。 <button type="button" id="show-help">查看說明</button> <button type="submit">送出需求</button> type="reset" 是回復表單控制項的初始值,不是關閉說明,也不是保留內容的取消按鈕。如果真的需要清除或回復,先讓訪客理解會失去哪些修改;不要只因按鈕寫「取消」,就套上 reset。 在 Vue、React 或其他元件中,也核對 type 是否真正傳到原生 button。元件外層寫了屬性,內部卻沒有向下傳遞,瀏覽器仍可能使用另一種預設行為。以渲染後的元素為準。 本例點同一種說明入口,結果不同 我們在自建桌機頁面放入合成姓名欄位和三個按鈕。第一個說明按鈕沒有指定 type,第二個明確使用 button,第三個是正式的示範提交。表單只向本機 GET 入口發送合成資料,沒有寄信或保存任何客戶內容。 點第一個按鈕後,瀏覽器到達提交入口,網址帶有姓名查詢值。回到原頁,點第二個按鈕,則只更新「說明已開啟;姓名與頁面保留」,網址仍停在原表單。 自建桌機示範:明確使用 button 的說明入口保留表單;未指定 type 的另一個入口會提交。 這個對照核的是原生類型與請求行為。你的表單若有必填欄位,未填時可能先被瀏覽器驗證擋下,所以畫面未跳轉不代表那顆按鈕不是 submit。使用合成有效資料,再核對是否發出請求,才不會被其他條件遮住。 有 click 腳本,仍可能繼續提交 一個 click 事件先開啟說明,並不會自動取消原本的提交動作。這也是為什麼頁面可能閃過說明,接著立刻跳走。若用途本來就不是提交,先修 type,再保留需要的事件處理。 event.preventDefault() 可取消適用事件的預設行為,但要知道取消的是哪個事件。只在某一個 click 處理器阻止,可能漏掉其他提交途徑;需要自訂真正的送出流程時,通常在 form 的 submit 事件統一處理。 也要查腳本是否主動呼叫提交、改變網址或執行路由跳轉。加上 button 只移除它本身的原生提交用途,不會阻止你自己的程式呼叫其他動作。原生行為與腳本行為各自記錄,才能找出真正的跳頁原因。...
HTML摺疊內容不用外掛:details與summary怎麼用?
頁面上的補充說明越寫越長,卻不是每位讀者都需要看完。若只是讓人自行展開網站使用方式、準備資料或技術細節,可以先用HTML的details與summary,不必為這個小功能另外裝外掛。 但摺疊不是把頁面「變短」的萬能辦法。費用、使用條件、重要警告等讀者必須知道的資訊,應保持可見;適合收起來的是補充內容。 先有一個清楚的展開入口 summary放在details裡,作為讀者看到的標籤;其餘內容放在後面。下面用「需要準備什麼?」示範一段補充說明,不需要JavaScript: <details> <summary>需要準備什麼?</summary> <p>準備頁面文字與圖片草稿,再整理需要補充的資料。</p> </details> 標籤要說明展開後會得到什麼。「需要準備什麼?」比「更多」容易判斷。也不要為了版面乾淨,把原本的展開標記與鍵盤焦點全部拿掉。 「需要準備什麼?」已展開,其他入口仍可見。重要提醒直接顯示,補充資料讓讀者自行展開。 預設展開,用open;關閉則拿掉它 想讓內容一開始就顯示,可以寫<details open>。open是布林屬性,是否存在才是重點:open="false"仍會展開,不能用這種寫法關閉。要預設關閉,就移除open。 網站可以用同樣方式收起「使用前要準備哪些資料?」等補充說明。圖中的「需要準備什麼?」展開後,其他問題仍保持原來的狀態。若只是各自開合,先維持這個簡單做法,不必急著加入只能開一項的行為。 用鍵盤走一次,不只試滑鼠 修改後,先按Tab找到每個問題,確認焦點清楚可見,再試空白鍵開合,並確認內容能正常讀到。手機上也要看長標籤是否換行、展開後會不會超出畫面。 把元件放回自己的網站後,還要連同主題樣式、其他腳本與實際使用方式一起檢查。不要只因為滑鼠點得開,就認定所有使用方式都沒問題。 保留一份修改前HTML,先在自己的測試頁加入元件。發現標籤看不懂、焦點消失或必要資訊被藏起來,就先恢復原本直接顯示的內容,再調整設計。 參考資料 MDN:details MDN:summary 資料查核:2026年10月2日。
- 1
- 2
