">
 

A tabbed form that silently refused to submit — required fields hidden behind another tab

Iniciado por joomlamz, Hoje at 02:25

Respostas: 1   |   Visualizações: 1

Tópico anterior - Tópico seguinte

0 Membros e 1 Visitante estão a ver este tópico.

Saudações, comunidade do **webmastersmz.com**! Como especialista em tecnologia, analisei o tópico em inglês com o título: *"A tabbed form that silently refused to submit — required fields hidden behind another tab"* (Um formulário com separadores que recusava silenciosamente ser submetido — campos obrigatórios ocultos atrás de outro separador).

Este é um problema clássico de Experiência de Utilizador (UX) aliado a falhas na validação *frontend*. Do ponto de vista estritamente técnico, este cenário gera frustração extrema e impacta negativamente a taxa de conversão (seja em registos, compras ou submissões de dados).

Aqui estão os **pontos principais** da análise técnica deste problema:

1. **Validação HTML5 vs. Visibilidade do DOM:**
   Muitos navegadores modernos executam a validação nativa de formulários (atributos como `required`) mesmo quando o elemento está oculto via CSS (`display: none` ou `visibility: hidden`). No entanto, o navegador frequentemente falha ao tentar focar (*focus*) ou rolar (*scroll*) até ao campo inválido se este estiver num separador inativo, resultando numa submissão "silenciosamente bloqueada" sem feedback visual para o utilizador.

2. **Falta de Feedback no *Frontend*:**
   Quando o evento de submissão (`submit`) é interceptado por JavaScript (por exemplo, via AJAX/Fetch API) e a validação customizada falha, o script muitas vezes cessa a execução sem disparar um alerta claro ou redirecionar o utilizador para o separador (*tab*) onde reside o erro.

3. **Boas Práticas de Resolução (Como mitigar):**
   * **Validação por Passos:** Em formulários divididos em abas, a submitância geral só deve ser ativada ou validada quando o utilizador avança explicitamente por cada etapa.
   * **Indicadores Visuais:** Adicionar indicadores claros nas abas (como um ícone de alerta ou contador de erros) para mostrar onde estão os campos pendentes.
   * **Gestão de Foco via JS:** Se a submissão falhar, o script deve identificar programaticamente em qual aba está o primeiro campo com erro, ativá-la e focar o elemento correspondente.

Este é um excelente caso de estudo sobre como pequenos detalhes de usabilidade podem quebrar uma aplicação web funcional. O que acham desta situação, estimados colegas? Já se depararam com bugs semelhantes em formulários complexos nos vossos projetos? Que estratégias costumam usar para garantir que os utilizadores não fiquem presos em formulários de múltiplos passos? **Deixem as vossas opiniões e experiências aqui nos comentários do fórum webmastersmz.com para enriquecermos este debate!**

---

Para garantir que os vossos projetos e fóruns rodam sem falhas, convido-vos a conhecer as soluções de alojamento de alta performance da AplicHost em [https://aplichost.com](https://aplichost.com).

A tabbed form that silently refused to submit — required fields hidden behind another tab



Tópico: A tabbed form that silently refused to submit — required fields hidden behind another tab
Categoria: Tutoriais | Programação & Tecnologia
Idioma Principal: Português (Conteúdo de Tecnologia)

Descrição do Conteúdo / Informações:
-------------------------------------------------------------------------


Background


The site edit modal kept accumulating fields — site name, category, SSH connection details, WordPress install location — until editing anything meant scrolling up and down a single long form to find the right field. To clean this up, we split it into three tabs: "Registration info," "SSH," and "WordPress info." That change broke form submission itself, in a way that was hard to spot at first.



What tabbing broke


The tab implementation itself is straightforward. Each tab's fields live in a <div class="site-tab-content" data-tab="...">, and CSS toggles which one is visible.

.site-tab-content { display: none; }
.site-tab-content.active { display: block; }

An inactive tab is hidden with display: none. Nothing unusual so far, and visually it worked fine.

The problem showed up when a required field sat in a tab that was not currently active, and the user left it empty while saving from a different tab. Clicking the save button did nothing. No error message appeared. The form just looked stuck.



Root cause: a browser cannot report an error on a field it cannot show


HTML5 form validation works by having the browser automatically block the submit event whenever a constrained field (like required) fails, then focusing that field and showing its standard validation bubble (equivalent to calling reportValidity()).

Note: reportValidity() is a method from the HTML5 Constraint Validation API. It checks whether a form element's value satisfies its constraints (required, pattern, etc.) and, if not, displays the browser's standard error bubble.

But when the failing field sits inside a tab hidden with display: none, the browser has nowhere to anchor that error bubble. It still faithfully blocks the submit — but it cannot visualize the error, so it simply stops without any visible feedback. From the user's side, this looks exactly like a button that does not respond.

Before tabbing, every field lived on the same screen, so this never surfaced. Introducing tabs — a UI pattern that deliberately limits what's visible at once — broke an implicit assumption the browser's built-in validation depends on: that an invalid field is always visible.



The fix: novalidate plus JS-driven validation


The fix was to stop relying on the browser's automatic blocking. Adding novalidate to the form disables that automatic block and guarantees the submit event always fires, letting JavaScript take full control of validation.

async function saveSite(e) {
e.preventDefault();
const form = e.target;

// If checkValidity() fails, switch to the tab containing the first
// :invalid element, then focus it and call reportValidity().
if (!form.checkValidity()) {
const firstInvalid = form.querySelector(':invalid');
if (firstInvalid) {
const tabContent = firstInvalid.closest('.site-tab-content');
if (tabContent && tabContent.getAttribute('data-tab')) {
switchSiteTab(tabContent.getAttribute('data-tab'));
}
// Focus/scroll may not work right after a tab switch,
// so call reportValidity() on the next frame.
requestAnimationFrame(() => {
try { firstInvalid.focus({ preventScroll: false }); } catch (_) {}
firstInvalid.reportValidity();
});
}
return;  // abort submission on validation failure
}
// ...normal save flow continues here
}

Two things matter here.


novalidate does not disable the constraints themselves. Attributes like required and pattern remain in effect, and both checkValidity() and the :invalid pseudo-class still work as expected. What novalidate disables is only the browser's automatic "block submit and show the error" behavior.


The code finds the first :invalid element and forces a switch to its tab before calling reportValidity(). By making the field visible first, the browser can render its error bubble without issue. Since a tab switch may not have finished painting yet, the code waits one frame via requestAnimationFrame before calling focus() and reportValidity().



A concrete case: making the SSH profile conditionally required


This mechanism paid off directly with the SSH profile requirement. The site edit modal has an "Update via browser only (no SSH)" checkbox; leaving it unchecked makes selecting an SSH profile mandatory. We wanted to prevent saving a site that satisfies neither option — no profile selected, and the checkbox left unchecked.

function toggleSSHFields() {
const isBrowserOnly = document.getElementById('browser_only_check').checked;
const profileSelect = document.getElementById('server_profile_select');

if (isBrowserOnly) {
// Browser-only mode doesn't need a profile
profileSelect.value = "";
profileSelect.removeAttribute('required');
} else {
// SSH mode requires a profile
profileSelect.setAttribute('required', '');
}
}

The required attribute is toggled dynamically based on the checkbox state, and the rest is handled by the cross-tab validation logic in saveSite described above. Trying to save without a profile selected automatically opens the "SSH" tab and shows the error bubble right on the profile dropdown. The user never has to hunt for why saving isn't working.



A side benefit: accessibility via ARIA


Alongside the tab split, we added role="tablist" / role="tab" / role="tabpanel" and aria-selected / aria-controls / aria-labelledby.

<div class="site-tabs" role="tablist" aria-label="Site edit tabs">
<button type="button" class="site-tab-btn active" role="tab"
id="site-tab-btn-info" aria-selected="true"
aria-controls="site-tab-panel-info">Registration info</button>
<!-- SSH and WordPress info tabs follow the same pattern -->
</div>

The tab-switching JavaScript keeps aria-selected in sync as well.

function switchSiteTab(tabName) {
form.querySelectorAll('.site-tab-btn').forEach(btn => {
const isActive = btn.getAttribute('data-tab-target') === tabName;
btn.classList.toggle('active', isActive);
btn.setAttribute('aria-selected', isActive ? 'true' : 'false');
});
}

It is not just a visual toggle — screen reader users also get told which tab is currently selected.



Wrap-up


Splitting a form into tabs feels like an intuitive cleanup, but it can quietly break an assumption the browser's built-in validation depends on: that an invalid field is always visible on screen. Disabling the automatic block with novalidate, keeping the constraint checks alive through checkValidity() / :invalid, and making the failing field visible before calling reportValidity() — that small extra step is what lets a tabbed UI coexist with standard form validation. It's a modest but broadly reusable pattern for any design that splits a form across multiple views.


Joomlamz
Consultoria em Informática
-------------------------------------------------------
Especialista em Sistemas Web & Manutenção de Servidores.
A desenvolver o novo AplPortal com suporte a PHP 8.
Precisa de ajuda profissional? Contacte-me.

Tags: