Novo

Novo

Novo

5 de setembro de 2025

5 de setembro de 2025

5 de setembro de 2025

5 de setembro de 2025

Tutorial

Tutorial

Tutorial

Personalize as Mensagens de Erro nos Formulários do Framer

Aprenda como substituir as mensagens de validação padrão do browser por tooltips personalizados nos formulários do Framer. Esse code override é compatível com o form builder nativo e oferece controle total sobre as mensagens de erro.

Compartilhe

Compartilhe

Compartilhe

Publicado por

Tooltip de validação personalizada para formulários do Framer.
Tooltip de validação personalizada para formulários do Framer.
Tooltip de validação personalizada para formulários do Framer.

Se você já tentou criar um formulário no Framer, provavelmente percebeu que as mensagens de erro padrão do navegador não são nada atraentes. Elas variam de browser para browser e não combinam com o design do seu site.

Com este code override, você pode substituir as mensagens padrão por tooltips personalizados, que aparecem ao lado do campo com erro. Assim, você mantém o estilo visual consistente e tem controle total sobre o texto e as cores.


Crie um novo arquivo

No seu projeto no Framer, vá até Assets → Code → New File para criar um novo arquivo de código.


Cole o código

Apague o conteúdo padrão e cole este código inteiro:

import { useEffect, useRef } from "react"
import type { Override } from "framer"

// ---- Altere se necessário ----
const DEFAULT_MESSAGES = {
    valueMissing: "Por favor, preencha este campo.",
    typeMismatchEmail: "Digite um e-mail válido.",
    typeMismatchUrl: "Digite uma URL válida.",
    patternMismatch: "O formato não corresponde ao esperado.",
    tooShort: "O valor está muito curto.",
    tooLong: "O valor está muito longo.",
    rangeUnderflow: "O valor é muito baixo.",
    rangeOverflow: "O valor é muito alto.",
    stepMismatch: "O valor não corresponde ao passo definido.",
    badInput: "Valor inválido.",
    generic: "Verifique este campo.",
}

// Elemento tooltip
function makeTooltip() {
    const el = document.createElement("div")
    el.className = "framer-custom-tooltip"
    // Estilo mínimo; ajuste livremente
    Object.assign(el.style, {
        position: "fixed",
        zIndex: "999999",
        maxWidth: "260px",
        fontFamily:
            "Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif",
        fontSize: "12px",
        lineHeight: "1.3",
        background: "#0055ff",
        color: "#fff",
        padding: "8px 10px",
        borderRadius: "8px",
        boxShadow: "0 6px 24px rgba(0,0,0,0.25)",
        pointerEvents: "none",
        transform: "translateY(-6px)",
        transition: "opacity 120ms ease, transform 120ms ease",
        opacity: "0",
    })
    document.body.appendChild(el)
    requestAnimationFrame(() => {
        el.style.opacity = "1"
        el.style.transform = "translateY(0)"
    })
    return el
}

function destroyTooltip(el?: HTMLElement | null) {
    if (el && el.parentNode) el.parentNode.removeChild(el)
}

function getMessageFor(
    input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
) {
    // Prioridade: data-error > built-in validity type > placeholder > generic
    const custom = input.getAttribute("data-error")
    if (custom) return custom

    const v = input.validity
    if (v.valueMissing) return DEFAULT_MESSAGES.valueMissing
    if (v.typeMismatch && (input as HTMLInputElement).type === "email")
        return DEFAULT_MESSAGES.typeMismatchEmail
    if (v.typeMismatch && (input as HTMLInputElement).type === "url")
        return DEFAULT_MESSAGES.typeMismatchUrl
    if (v.patternMismatch) return DEFAULT_MESSAGES.patternMismatch
    if (v.tooShort) return DEFAULT_MESSAGES.tooShort
    if (v.tooLong) return DEFAULT_MESSAGES.tooLong
    if (v.rangeUnderflow) return DEFAULT_MESSAGES.rangeUnderflow
    if (v.rangeOverflow) return DEFAULT_MESSAGES.rangeOverflow
    if (v.stepMismatch) return DEFAULT_MESSAGES.stepMismatch
    if (v.badInput) return DEFAULT_MESSAGES.badInput

    const ph = (input as HTMLInputElement).placeholder
    if (ph) return ph
    return DEFAULT_MESSAGES.generic
}

function positionTooltipNear(input: Element, tip: HTMLElement) {
    const r = input.getBoundingClientRect()
    // posiciona acima e à direita por padrão; mantém dentro do viewport
    const margin = 8
    let top = r.top - tip.offsetHeight - margin
    let left = r.left + Math.min(16, Math.max(0, r.width - tip.offsetWidth))

    // Se cortado acima, posiciona abaixo
    if (top < 8) top = r.bottom + margin
    // Mantém dentro do viewport horizontalmente
    left = Math.max(8, Math.min(left, window.innerWidth - tip.offsetWidth - 8))

    tip.style.top = `${Math.round(top)}px`
    tip.style.left = `${Math.round(left)}px`
}

export function FormWithCustomTooltips(): Override {
    const ref = useRef<HTMLElement | null>(null)
    const tipRef = useRef<HTMLElement | null>(null)
    const currentFieldRef = useRef<HTMLElement | null>(null)

    useEffect(() => {
        // Se aplicado no próprio form, usa o ref.current diretamente
        const formEl = (
            ref.current?.tagName === "FORM"
                ? ref.current
                : ref.current?.closest("form")
        ) as HTMLFormElement | null

        if (!formEl) return
        formEl.setAttribute("novalidate", "true")

        const allFields = formEl.querySelectorAll<
            HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
        >("input, textarea, select")

        const show = (
            field: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
        ) => {
            // Atualiza o texto de validade customizada (afeta leitores de tela e API de constraint)
            const msg = getMessageFor(field)
            field.setCustomValidity(msg)

            // Tooltip visual
            destroyTooltip(tipRef.current)
            const tip = makeTooltip()
            tip.textContent = msg
            positionTooltipNear(field, tip)
            tipRef.current = tip
            currentFieldRef.current = field
        }

        const hide = () => {
            destroyTooltip(tipRef.current)
            tipRef.current = null
            currentFieldRef.current = null
        }

        const onInput = (e: Event) => {
            const field = e.target as
                | HTMLInputElement
                | HTMLTextAreaElement
                | HTMLSelectElement
            field.setCustomValidity("") // limpa assim que o usuário interage
            hide()
        }

        const onInvalid = (e: Event) => {
            e.preventDefault() // para a bolha do Chrome
            const field = e.target as
                | HTMLInputElement
                | HTMLTextAreaElement
                | HTMLSelectElement
            show(field)
        }

        const onSubmit = (e: Event) => {
            hide()

            // Força a checagem de validade antes de submeter
            const isValid = formEl.checkValidity()

            if (!isValid) {
                e.preventDefault() // Para o submit
                e.stopPropagation() // Para a propagação

                // Foca no primeiro campo inválido
                const firstInvalid = Array.from(allFields).find(
                    (f) => !f.checkValidity()
                )
                if (firstInvalid) {
                    firstInvalid.focus({ preventScroll: false })
                    show(firstInvalid)
                }
                return false
            }
        }

        // Define handlers que podem ser removidos adequadamente
        const handleResize = () => {
            if (tipRef.current && currentFieldRef.current) {
                positionTooltipNear(currentFieldRef.current, tipRef.current)
            }
        }

        const handleScroll = () => {
            if (tipRef.current && currentFieldRef.current) {
                positionTooltipNear(currentFieldRef.current, tipRef.current)
            }
        }

        // Conecta os eventos
        allFields.forEach((f) => {
            f.addEventListener("invalid", onInvalid)
            f.addEventListener("input", onInput)
            f.addEventListener("blur", onInput)
        })
        formEl.addEventListener("submit", onSubmit)
        window.addEventListener("resize", handleResize)
        window.addEventListener("scroll", handleScroll, true)

        return () => {
            hide()
            allFields.forEach((f) => {
                f.removeEventListener("invalid", onInvalid)
                f.removeEventListener("input", onInput)
                f.removeEventListener("blur", onInput)
            })
            formEl.removeEventListener("submit", onSubmit)
            window.removeEventListener("resize", handleResize)
            window.removeEventListener("scroll", handleScroll, true)
        }
    }, [])

    // Anexa a qualquer elemento dentro do seu formulário (ex: o frame ou wrapper do form)
    return {
        ref: (node: HTMLElement | null) => {
            ref.current = node
        },
    }
}


Personalize

Você só precisa mexer em dois pontos dentro da função makeTooltip():

background: "#0055ff", // cor de fundo da tooltip
color: "#fff",         // cor do texto dentro da tooltip

Você pode usar:

  • Nomes: red, blue, black, white

  • Hexadecimal: #000000 (preto), #ffffff (branco)

  • RGB: rgb(0, 85, 255)

Dica: garanta contraste suficiente entre background e color para leitura fácil. Em geral, texto claro em fundo escuro vice-versa.

Mensagens de erro

Você também pode alterar as mensagens de erro por campo. Encontre esse bloco no início do código e altere o texto, sem deletar nenhum outro elemento:

// ---- Altere se necessário ----
const DEFAULT_MESSAGES = {
    valueMissing: "Por favor, preencha este campo.",
    typeMismatchEmail: "Digite um e-mail válido.",
    typeMismatchUrl: "Digite uma URL válida.",
    patternMismatch: "O formato não corresponde ao esperado.",
    tooShort: "O valor está muito curto.",
    tooLong: "O valor está muito longo.",
    rangeUnderflow: "O valor é muito baixo.",
    rangeOverflow: "O valor é muito alto.",
    stepMismatch: "O valor não corresponde ao passo definido.",
    badInput: "Valor inválido.",
    generic: "Verifique este campo.",
}


Aplique o override

Siga os passos abaixo para aplicar o efeito no seu formulário:

  1. Selecione a camada de texto que está conectada a um campo do CMS.

  2. Vá para a aba "Code" > "Overrides".

  3. Escolha o arquivo de código criado.

  4. Aplique o override "FormWithCustomTooltips"


Pronto! As mensagens de validação do navegador serão substituídas pelo seu tooltip personalizado.

Design e performance juntos
Design e performance juntos

Crie sua conta gratuita e construa sites profissionais com o Framer, sem código.

Crie sua conta gratuita e construa sites profissionais com o Framer, sem código.