Docs›Reatividade›Diretivas mad-*
Reatividade

Diretivas mad-*

mad:click, mad:model, mad:change, mad-bind, mad-text, mad-if, mad-show.

O MAD expõe duas famílias de diretivas no Blade, e a diferença entre elas é a coisa mais importante a entender nesta página:

mad:click="onSalvar"        ← FAMILIA 1: server action (POST AJAX, dois pontos)
mad-on:click="contador++"   ← FAMILIA 2: Alpine puro client-side (hifen, sem POST)
mad:* — Server actions PHP
Dispara um POST AJAX para MadComponentHandler, executa um método PHP, atualiza state. Sintaxe: dois pontos (mad:click).
mad-* — Alpine reativo JS
Alias de x-* do Alpine.js, 100% client-side. Sintaxe: hífen (mad-text, mad-on:click).

Diretivas de server action (mad:*)

Convertidas para data-mad-* em tempo de compilação (Mad\View\MadBlade) e processadas no client por packages/mad-framework/assets/mad-livewire.js. Cada uma dispara um POST para o endpoint do componente.

DiretivaDispara emResolve para
mad:click="metodo"clickdata-mad-click
mad:model="prop"próximo POST do wrapperdata-mad-model
mad:model.live="prop"input, debounced 300msdata-mad-model-live
mad:submit="metodo"submit do <form>data-mad-submit
mad:change="metodo"change (select/input/checkbox)data-mad-change
mad:loadingvisível só durante a requisiçãodata-mad-loading
mad:loading.removeescondido durante a requisiçãodata-mad-loading-remove

mad:click

Chama um método público do MadComponent atual.

<mad-btn mad:click="incrementar">+1</mad-btn>
<mad-btn mad:click="resetar" variant="ghost">Resetar</mad-btn>

Com parâmetros — literais (number/string/bool) ou interpolados do Blade:

MAD__BLADE_COMMENT__1__
<mad-btn mad:click="aprovar(true)">Aprovar</mad-btn>
<mad-btn mad:click="onEditar({{ $pedido->id }})">Editar</mad-btn>
<mad-btn mad:click="onMudarStatus('cancelado')">Cancelar</mad-btn>

MAD__BLADE_COMMENT__2__
public function onEditar(int $id): MadResponse
{
    $pedido = Pedido::findOrFail($id);
    // ...
}
Resolução de parâmetros

Os parâmetros de mad:click="metodo(1, 'x')" são parseados como JSON no client (mad-livewire.js) e enviados posicionalmente em mad_params. O PHP resolve por posição/tipo via reflection — mesmo mecanismo usado em mount(array $params).

Atalhos mágicos — $set e $refresh

Quando a única coisa que uma action faria é setar uma prop, pule o método PHP inteiramente — $set resolve direto no client e dispara um POST sem mad_action (só mad_model com os valores).

MAD__BLADE_COMMENT__3__
<mad-btn mad:click="$set('periodo', 'mes')">Este mes</mad-btn>
<mad-btn mad:click="$set('periodo', 'dia')">Hoje</mad-btn>

MAD__BLADE_COMMENT__4__
<mad-btn mad:click="$set({ periodo: 'mes', categoria: 'todas' })">Resetar filtros</mad-btn>

MAD__BLADE_COMMENT__5__
<mad-btn mad:click="$refresh">Atualizar</mad-btn>

mad:model e mad:model.live

Two-way binding entre um input e uma prop pública. Detalhes completos em Two-way binding.

MAD__BLADE_COMMENT__6__
<input type="text" mad:model="nome" value="{{ $that->nome }}">

MAD__BLADE_COMMENT__7__
<input type="text" mad:model.live="busca" value="{{ $that->busca }}"
       placeholder="Buscar produto...">

MAD__BLADE_COMMENT__8__
<mad-input-field name="nome" label="Nome" />
<mad-money-field name="valor" label="Valor" mad:model.live="valor" />

mad:submit

<form mad:submit="onSalvar">
    <mad-input-field name="nome" label="Nome" required />
    <mad-money-field name="valor" label="Valor" required />
    <mad-form-actions>
        <mad-btn type="submit" variant="primary">Salvar</mad-btn>
    </mad-form-actions>
</form>
public function onSalvar(): MadResponse
{
    $produto = Produto::findOrNew($this->registroId);
    $this->form->save($produto);

    return (new MadResponse())->toast('Salvo!', 'success')->closeDrawer();
}

mad:change

Útil para cascatas (estado → cidade, categoria → produto). O valor atual do elemento vai como último parâmetro:

MAD__BLADE_COMMENT__9__
<mad-dbselect-field name="estado" model="Estado" display="nome"
    mad:change="onChangeEstado" />
// mad:change="onChangeEstado" -> onChangeEstado($value) — $value = valor do <select>
public function onChangeEstado(string $value): MadResponse
{
    $this->cidade = '';
    $html = MadBlade::render('components.dbcombo-field', [
        'name' => 'cidade', 'label' => 'Cidade', 'display' => 'nome',
        'query' => Cidade::where('estado_id', $value)->orderBy('nome'),
    ]);

    return (new MadResponse())->html('[data-mad-target="cidade-combo"]', $html);
}

data-mad-loading e data-mad-loading-remove

Toda requisição desabilita automaticamente todo [data-mad-click] e os botões de submit do form de origem — mad:loading/ .remove dão controle fino sobre o que mais aparece ou some durante o request.

<mad-btn mad:click="onSalvar" mad:loading>
    Salvar
</mad-btn>

MAD__BLADE_COMMENT__10__
<i data-lucide="check-circle" mad:loading.remove></i>

MAD__BLADE_COMMENT__11__
<span mad:loading class="mad-spinner"></span>
<span mad:loading.remove>Pronto</span>

data-mad-confirm — confirmação antes do clique

Não é uma diretiva mad:* (não tem prefixo reescrito) — é um atributo HTML simples que o mesmo listener de mad:click lê antes de disparar o POST. Usa window.madConfirm se disponível (dialog estilizado), com fallback para confirm() nativo.

MAD__BLADE_COMMENT__12__
<mad-btn mad:click="onExcluir({{ $produto->id }})"
    data-mad-confirm="Excluir este produto? Essa ação não pode ser desfeita.">
    Excluir
</mad-btn>

Atributos reservados — nunca reescritos

O wrapper de todo MadComponent carrega estes atributos de controle interno. Não os declare manualmente nem os use como nome de prop:

mad-component   identifica o wrapper do MadComponent no DOM
mad-id          ID unico da instancia (usado em querySelector/escopo de ops)
mad-state       state serializado + criptografado (AES-256-GCM)
mad-endpoint    URL do wire endpoint (/app/_mad-wire ou /public/_mad-wire)
mad-action      nome da action — uso interno do handler
mad-params      params extras — uso interno do handler
mad-form        id do form associado
mad-name        identificador generico

Diretivas Alpine reativas (mad-*)

Família separada: aliases 1:1 de x-* do Alpine.js, reescritos client-side por um plugin (mad-ui.js) que roda em alpine:init e via MutationObserver para fragmentos injetados depois (drawer, modal, morph). Combinadas com a op bind do MadWire, qualquer mudança de prop pública server-side propaga pro Alpine state automaticamente — sem full re-render.

Padrão MADAlpine equivalenteUso
mad-data="..."x-dataEstado reativo (normalmente via @madWire)
mad-init="..."x-initCódigo de inicialização
mad-text="prop"x-textTexto reativo
mad-html="prop"x-htmlHTML reativo (cuidado com XSS)
mad-show="cond"x-showVisibilidade (display:none)
mad-if="cond"x-if (dentro de <template>)Render condicional
mad-for="x in lista"x-for (dentro de <template>)Loop
mad-bind:attr="..."x-bind:attrBind de atributo/classe/style
mad-on:evt="..."x-on:evtEvent listener client puro
mad-model="prop"x-modelTwo-way binding (Alpine puro — não sai do navegador)
mad-cloakx-cloakEsconde até Alpine inicializar
mad-ref="nome"x-refReferência ($refs.nome)
mad-effect="..."x-effectEfeito reativo
mad-ignorex-ignoreIgnorar Alpine nesse nó
mad-transitionx-transitionTransições de show/hide
mad-modelablex-modelableExpor prop de componente Alpine para x-model externo
mad-teleportx-teleportPortal de DOM (Alpine, não confundir com MadResponse->teleport())

@madWire — estado reativo a partir das props PHP

Lê props públicas do MadComponent atual via MadRenderContext e emite mad-data='{"prop":valor}' — o plugin reescreve para x-data, criando um scope Alpine com os valores iniciais já vindos do PHP.

<div @madWire(['contador', 'mensagem'])>
    <div mad-text="contador" style="font-size:3rem;"></div>
    <span mad-text="contador > 5 ? 'Muito!' : 'Pouco'"></span>

    <template mad-if="mensagem">
        <div class="mad-alert" mad-text="mensagem"></div>
    </template>

    <div mad-show="contador > 0">Tem valor</div>

    <button mad:click="incrementar">+1 servidor</button>
    <button mad-on:click="contador++">+1 cliente (só visual)</button>
</div>

Quando uma action server muda $this->contador, o handler emite uma op bind que atualiza tanto qualquer [data-mad-bind="contador"] quanto o Alpine state de qualquer [x-data] do wrapper que contenha a prop — mad-text, mad-if, mad-show re-renderizam sozinhos, sem POST novo e sem full re-render.

mad-for — loop reativo

<div @madWire(['alertas'])>
    <template mad-if="alertas.length > 0">
        <ul>
            <template mad-for="alerta in alertas">
                <li mad-text="alerta.mensagem"></li>
            </template>
        </ul>
    </template>
    <template mad-if="alertas.length === 0">
        <p>Nenhum alerta.</p>
    </template>
</div>

mad-bind:*, mad-on:*, mad-cloak

<div @madWire(['status', 'online'])>
    MAD__BLADE_COMMENT__13__
    <span mad-bind:class="online ? 'badge-success' : 'badge-danger'"
          mad-text="online ? 'Online' : 'Offline'"></span>

    MAD__BLADE_COMMENT__14__
    <input mad-on:keyup.enter="$refs.busca.focus()" mad-ref="busca">

    MAD__BLADE_COMMENT__15__
    <div mad-cloak mad-show="status === 'pronto'">Pronto!</div>
</div>

@madBind — patch direto, sem Alpine

Mais leve que @madWire para mostrar um valor escalar em vários pontos da tela: emite <span data-mad-bind="prop">valor</span>, atualizável pela op bind sem precisar de scope Alpine nenhum.

@madBind('contador')
CenárioUsar
Texto simples, valor escalar direto@madBind('prop') (mais leve)
Render condicional (@if → mad-if)@madWire + mad-if
Transformação (ternário, formatação)@madWire + mad-text="expr"
Class/style/atributo dinâmico@madWire + mad-bind:*
Loop reativo@madWire + mad-for
Bind de input puro client (rascunho não persistido)@madWire + mad-model

mad-model vs mad:model — a confusão mais comum

O nome é quase igual e o separador é a única diferença visual — mas o comportamento é completamente diferente:

MAD__BLADE_COMMENT__16__
<div @madWire(['rascunho'])>
    <textarea mad-model="rascunho"></textarea>
    <span mad-text="rascunho.length + ' caracteres'"></span>
</div>

MAD__BLADE_COMMENT__17__
<input type="text" mad:model="nome" value="{{ $that->nome }}">
Hífen = client. Dois pontos = server.

mad-model (hífen) é x-model do Alpine — nunca sai do navegador, não existe em PHP. mad:model (dois pontos) sincroniza com uma prop pública do MadComponent no próximo POST. A mesma regra vale para mad-on:click (JS puro) vs mad:click (action PHP).

NUNCA fazer

MAD__BLADE_COMMENT__18__
<button mad-click="contador++">+1</button>

MAD__BLADE_COMMENT__19__
<button mad-on:click="contador++">+1 cliente</button>
<button mad:click="onIncrementar">+1 servidor</button>

MAD__BLADE_COMMENT__20__
<input mad-model="nome">  MAD__BLADE_COMMENT__21__

MAD__BLADE_COMMENT__22__
<input mad:model="nome" value="{{ $that->nome }}">

MAD__BLADE_COMMENT__23__
<div @madWire(['contador'])>
    @madBind('contador') MAD__BLADE_COMMENT__24__
    <span mad-text="contador"></span>
</div>

MAD__BLADE_COMMENT__25__
<span data-mad-bind="contador">@madBind('contador')</span>

Próximos passos