Adicionar interações estáticas
BlogBlade gera HTML estático. Interações opcionais podem ser adicionadas com um
arquivo JavaScript em Content/assets/js/, sem introduzir servidor, framework ou
um runtime obrigatório. O conteúdo e os controles devem continuar compreensíveis
quando JavaScript estiver desabilitado.
Convenção de carregamento
Crie um arquivo, por exemplo Content/assets/js/interactions.js. O build publica
o arquivo com hash; nunca escreva o nome final diretamente no template:
@if ($assets->has('js/interactions.js'))
<script type="module" src="{{ $siteUrl->asset($assets->path('js/interactions.js')) }}"></script>
@endif
Para manter o comportamento previsível, cada receita deve limitar sua busca a um
elemento raiz ([data-interaction]), registrar listeners de forma idempotente e
devolver uma função de limpeza quando for inicializada novamente. Um módulo
pequeno pode expor este formato:
export function init(root = document) {
// listeners e estado da interação
return () => {
// remove listeners e desfaz estado temporário
};
}
const cleanup = init();
Não é necessário combinar todas as receitas em um único módulo. Use arquivos separados quando páginas diferentes precisarem de interações diferentes.
Tema claro e escuro
O HTML deve declarar uma preferência inicial e oferecer um botão que funcione
com teclado. O estado pode ser persistido em localStorage, mas a página não
deve depender dele para renderizar:
<button type="button" data-theme-toggle aria-pressed="false">
Alternar tema
</button>
const button = document.querySelector('[data-theme-toggle]');
if (button) {
const apply = (theme) => {
document.documentElement.dataset.theme = theme;
button.setAttribute('aria-pressed', String(theme === 'dark'));
};
const stored = localStorage.getItem('theme');
apply(stored || (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'));
button.addEventListener('click', () => {
const next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
localStorage.setItem('theme', next);
apply(next);
});
}
Defina as cores para [data-theme="dark"] no CSS e preserve contraste. O
conteúdo continua disponível se o script não carregar; somente a preferência
manual deixa de ser aplicada.
Sumário navegável
Para uma página com títulos, gere no Blade uma lista de links para IDs estáveis. O navegador já oferece a navegação sem JavaScript:
<nav aria-label="Sumário">
<ol>
@foreach ($documentHeadings as $heading)
<li><a href="#{{ $heading->id }}">{{ $heading->text }}</a></li>
@endforeach
</ol>
</nav>
Use JavaScript apenas para aprimoramentos, como destacar o título visível com
IntersectionObserver. Não esconda títulos nem impeça que os links âncora
funcionem quando JavaScript estiver desabilitado.
Filtros client-side simples
Renderize todos os itens no HTML e acrescente atributos de dados ao critério:
<label>Filtrar <input type="search" data-filter-input="posts"></label>
<ul data-filter-list="posts">
<li data-filter-value="blade,mdx"><a href="/postagens/blade/">Blade no conteúdo</a></li>
</ul>
<p data-filter-empty="posts" hidden>Nenhum resultado.</p>
O script pode alternar hidden nos itens e na mensagem vazia. O filtro é um
atalho: sem JavaScript, a lista completa permanece visível e utilizável. Use
aria-live="polite" para anunciar a quantidade filtrada se isso for relevante.
Copiar link
Use um botão explícito, com texto que descreva a ação, e mantenha uma alternativa
manual (window.prompt ou o endereço visível) quando a Clipboard API não estiver
disponível:
<button type="button" data-copy-link="/artigos/exemplo/">
Copiar link
</button>
<span data-copy-status aria-live="polite"></span>
document.querySelectorAll('[data-copy-link]').forEach((button) => {
button.addEventListener('click', async () => {
const url = new URL(button.dataset.copyLink, window.location.href).href;
const status = document.querySelector('[data-copy-status]');
try {
await navigator.clipboard.writeText(url);
if (status) status.textContent = 'Link copiado.';
} catch {
window.prompt('Copie este link:', url);
}
});
});
O botão é um aprimoramento; ele não deve ser a única forma de descobrir ou compartilhar a URL. Em páginas sensíveis, considere também o contexto seguro exigido pela Clipboard API e não anuncie sucesso antes da operação terminar.
Checklist de acessibilidade
- Prefira elementos semânticos (
button,nav,label) adivclicáveis. - Garanta foco visível e operação completa pelo teclado.
- Use
aria-livesomente para mudanças de estado relevantes e breves. - Respeite
prefers-reduced-motionem transições e rolagens suaves. - Teste cada página com JavaScript desabilitado: conteúdo, links e formulários ainda devem funcionar.
Consulte Gerenciar assets para a publicação com
hash e Blade no Pocket Banana para as variáveis
$assets e $siteUrl.