# Slots

Esta página assume que você já leu o Básico sobre Componentes. Se você não está familizariado com componentes, recomendamos lê-lo primeiro.

Aprenda o básico sobre slots com uma aula gratuita na Vue School

# Conteúdo do Slot

Vue implementa uma API de distribuição de conteúdo inspirada no rascunho da especificação de Web Components (opens new window), utilizando o elemento <slot> como saída de distribuição de conteúdo.

Isto permite a criação de componentes como o seguinte exemplo:

<todo-button> Adicionar afazer </todo-button> 
1
2
3

E, no template de <todo-button>, possuímos:

<!-- template do componente todo-button --> <button class="btn-primary"> <slot></slot> </button> 
1
2
3
4

Quando o componente renderizar, <slot></slot> será substituído por "Adicionar afazer".

<!-- HTML renderizado --> <button class="btn-primary"> Adicionar afazer </button> 
1
2
3
4

Strings são apenas um exemplo simples! Slots podem conter código de template também, incluindo HTML:

<todo-button> <!-- Adiciona um ícone do Font Awesome --> <i class="fas fa-plus"></i> Adicionar afazer </todo-button> 
1
2
3
4
5

Ou, até mesmo, outros componentes:

<todo-button> <!-- Utilizando um componente para adicionar um ícone --> <font-awesome-icon name="plus"></font-awesome-icon> Adicionar afazer </todo-button> 
1
2
3
4
5

Se o template de <todo-button> não possuísse o elemento <slot>, qualquer conteúdo fornecido seria descartado.

<!-- template do componente todo-button, agora sem <slot></slot> --> <button class="btn-primary"> Criar um novo item </button> 
1
2
3
4
<todo-button> <!-- O texto a seguir não será renderizado --> Adicionar afazer </todo-button> 
1
2
3
4

# Escopo da Renderização

Quando você desejar utilizar dados dentro de um slot, como em:

<todo-button> Remover {{ item.name }} </todo-button> 
1
2
3

Este slot possui acesso às propriedades da mesma instância (isto é, mesmo "escopo"), que o resto do template.

Diagrama para explicar o Escopo da Renderização

O slot não possui acesso ao escopo de <todo-button>. Por exemplo, se tentarmos adquirir o valor de action, não será possível:

<todo-button action="delete"> Ao clicar aqui, este item realizará a ação {{ action }} <!-- `action` retornará `undefined` neste caso, já que o conteúdo é apenas transmitido para `<todo-button>`, ao invés de ser definido como um dado de `<todo-button>`. --> </todo-button> 
1
2
3
4
5
6
7
8

Lembre-se, como regra, de que:

Tudo do template pai (parent template) é compilado no escopo pai (parent scope); tudo do template filho (child template), é compilado no escopo filho (child scope).

# Conteúdo Padrão

Há certos casos onde é interessante definir um conteúdo padrão (ou de fallback) para um slot — ou seja, conteúdo que será apenas renderizado caso nenhum outro for informado. Por exemplo, no componente <submit-button>:

<button type="submit"> <slot></slot> </button> 
1
2
3

Podemos querer mostrar "Enviar" dentro do <button> quando nenhum outro texto for informado. Para isto, basta apenas colocar "Enviar" entre as tags <slot> para defini-lo como o conteúdo padrão.

<button type="submit"> <slot>Enviar</slot> </button> 
1
2
3

Agora, ao utilizarmos o componente <submit-button>, sem prover um conteúdo:

<submit-button></submit-button> 
1

"Enviar" será renderizado, por ser o conteúdo padrão:

<button type="submit"> Enviar </button> 
1
2
3

Entretanto, se especificarmos um conteúdo:

<submit-button> Salvar </submit-button> 
1
2
3

O conteúdo informado que será renderizado no lugar de "Enviar":

<button type="submit"> Salvar </button> 
1
2
3

# Slots Nomeados

Há casos onde é interessante utilizar vários slots. Por exemplo, em um componente <base-layout>, com o seguinte template:

<div class="container"> <header> <!-- Queremos nosso conteúdo para o cabeçalho aqui --> </header> <main> <!-- Queremos nosso conteúdo principal aqui --> </main> <footer> <!-- Queremos nosso conteúdo para o rodapé aqui --> </footer> </div> 
1
2
3
4
5
6
7
8
9
10
11

Para estes casos, o elemento <slot> possui um atributo especial, name, que pode ser utilizado para designar uma identificação única a cada <slot> e, assim, determinar onde certos conteúdos devem ser renderizados:

<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> 
1
2
3
4
5
6
7
8
9
10
11

Um elemento <slot> sem o atributo name, implicitamente, possui name="default".

Para fornecer conteúdo a um slot nomeado, precisamos utilizar a diretiva v-slot em um elemento <template>, indicando o nome do slot como argumento, logo em seguida:

<base-layout> <template v-slot:header> <h1>Aqui podemos ter um título para a página.</h1> </template> <template v-slot:default> <p>Um parágrafo para o conteúdo principal.</p> <p>E aqui mais um.</p> </template> <template v-slot:footer> <p>Aqui temos informações para contato.</p> </template> </base-layout> 
1
2
3
4
5
6
7
8
9
10
11
12
13
14

A partir deste momento, o conteúdo dos elementos <template> será transferido para os slots correspondentes.

O HTML renderizado será:

<div class="container"> <header> <h1>Aqui podemos ter um título para a página.</h1> </header> <main> <p>Um parágrafo para o conteúdo principal.</p> <p>E aqui mais um.</p> </main> <footer> <p>Aqui temos informações para contato.</p> </footer> </div> 
1
2
3
4
5
6
7
8
9
10
11
12

É importante destacar que v-slot pode ser adicionado apenas à um <template> (exceto neste caso).

# Definição de Escopo em Slots

Em certos momentos, é interessante acessar dados apenas disponíveis no componente filho (child component) no conteúdo de um slot. Um caso comum é utilizarmos um componente para renderizarmos itens de um Array, e queremos o poder de personalizar como cada item é renderizado.

Por exemplo, em um componente que possui uma lista de afazeres:

app.component('todo-list', { data() { return { items: ['Alimentar o gato', 'Comprar leite'] } }, template: ` <ul> <li v-for="(item, index) in items"> {{ item }} </li> </ul> ` }) 
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Podemos desejar substituir o {{ item }} com um <slot> para customizá-lo no componente pai:

<todo-list> <i class="fas fa-check"></i> <span class="green">{{ item }}</span> </todo-list> 
1
2
3
4

Entretanto, isto não funcionará por estarmos definindo o conteúdo do slot no componente pai, e apenas o componente <todo-list> possuir acesso ao dado item.

Para fazer com que item esteja disponível para uso no conteúdo do slot definido no componente pai, podemos passá-lo como um atributo de <slot>, através de v-bind:

<ul> <li v-for="( item, index ) in items"> <slot :item="item"></slot> </li> </ul> 
1
2
3
4
5

Você pode vincular quantos atributos precisar no slot:

<ul> <li v-for="( item, index ) in items"> <slot :item="item" :index="index" :another-attribute="anotherAttribute"></slot> </li> </ul> 
1
2
3
4
5

Atributos passados para um elemento <slot> são chamados de props do slot (slot props). Agora, no escopo do componente pai (parent scope), podemos utilizar v-slot com um valor, que será utilizado, neste caso, como o nome de acesso à estes props do slot:

<todo-list> <template v-slot:default="slotProps"> <i class="fas fa-check"></i> <span class="green">{{ slotProps.item }}</span> </template> </todo-list> 
1
2
3
4
5
6
Diagrama para explicar a Definição de Escopo em Slots

Neste exemplo, escolhemos slotProps como valor, a fim de tê-lo como o nome de acesso aos props do slot. É possível utilizar qualquer nome que desejar.

# Sintaxe Abreviada para Slot Único e Default

Em casos como o acima, quando apenas o slot padrão (default) é fornecido, podemos utilizar o próprio conteúdo do componente como o template do slot. Deste modo, utilizamos v-slot diretamente no componente:

<todo-list v-slot:default="slotProps"> <i class="fas fa-check"></i> <span class="green">{{ slotProps.item }}</span> </todo-list> 
1
2
3
4

Podemos deixar isto ainda mais simples, já que, assim como não especificar um name para um elemento <slot> o faz possuir name="default", não especificar um argumento para um v-slot o faz referenciar default, implicitamente:

<todo-list v-slot="slotProps"> <i class="fas fa-check"></i> <span class="green">{{ slotProps.item }}</span> </todo-list> 
1
2
3
4

É importante mencionar que não é possível utilizar esta sintaxe abreviada para slot padrão em conjunto de slots nomeados, já que isto resultaria em ambiguidade de escopo (scope ambiguity):

<!-- INVÁLIDO, resultará em um aviso --> <todo-list v-slot="slotProps"> <i class="fas fa-check"></i> <span class="green">{{ slotProps.item }}</span> <template v-slot:other="otherSlotProps"> slotProps NÃO está disponível aqui </template> </todo-list> 
1
2
3
4
5
6
7
8
9

Sempre que houver mais de um slot, utilize da sintaxe completa, com <template>, para todos os slots:

<todo-list> <template v-slot:default="slotProps"> <i class="fas fa-check"></i> <span class="green">{{ slotProps.item }}</span> </template> <template v-slot:other="otherSlotProps"> ... </template> </todo-list> 
1
2
3
4
5
6
7
8
9
10

# Desestruturando Props do Slot

Internamente, slots com escopo definido encapsulam o conteúdo do seu respectivo slot em uma função com um único argumento:

function (slotProps) { // ... conteúdo do slot ... } 
1
2
3

Isto significa que o valor passado à v-slot pode aceitar, na verdade, qualquer expressão JavaScript válida na definição de argumentos de uma função. Portanto, você também pode utilizar a desestruturação de objetos, da ES2015 (opens new window), a fim de trabalhar apenas com os props que você desejar no conteúdo do slot, como no exemplo abaixo:

<todo-list v-slot="{ item }"> <i class="fas fa-check"></i> <span class="green">{{ item }}</span> </todo-list> 
1
2
3
4

Isto pode tornar o template muito mais limpo, especialmente quando o slot fornece vários props. Isto também abre portas para outras possibilidades, como renomear um prop de nome item para todo:

<todo-list v-slot="{ item: todo }"> <i class="fas fa-check"></i> <span class="green">{{ todo }}</span> </todo-list> 
1
2
3
4

Você pode até definir valores padrão (fallbacks), a fim de serem utilizados quando um prop do slot não possuir um valor definido (undefined):

<todo-list v-slot="{ item = 'Isto é um afazer placeholder' }"> <i class="fas fa-check"></i> <span class="green">{{ item }}</span> </todo-list> 
1
2
3
4

# Nomes Dinâmicos para Slots

Argumentos dinâmicos de diretiva também funcionam com v-slot, permitindo a definição de nomes dinâmicos para slots:

<base-layout> <template v-slot:[dynamicSlotName]> ... </template> </base-layout> 
1
2
3
4
5

# Forma Abreviada para Slots Nomeados

Assim como em v-on e v-bind, v-slot também possui uma forma abreviada, basta substituirmos v-slot: (ou seja, tudo antes do argumento) pelo símbolo especial #. Portanto, v-slot:header também pode ser escrito como #header, por exemplo.

<base-layout> <template #header> <h1>Aqui podemos ter um título para a página.</h1> </template> <template #default> <p>Um parágrafo para o conteúdo principal.</p> <p>E aqui mais um.</p> </template> <template #footer> <p>Aqui temos informações para contato.</p> </template> </base-layout> 
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Entretanto, assim como em qualquer outra diretiva, a forma abreviada está apenas disponível quando esta recebe um argumento. Isto significa que o exemplo a seguir possui sintaxe inválida:

<!-- Isto acionará um aviso --> <todo-list #="{ item }"> <i class="fas fa-check"></i> <span class="green">{{ item }}</span> </todo-list> 
1
2
3
4
5
6

Deste modo, você sempre precisa especificar o nome do slot que você deseja definir ao utilizar a forma abreviada, como em:

<todo-list #default="{ item }"> <i class="fas fa-check"></i> <span class="green">{{ item }}</span> </todo-list> 
1
2
3
4