# 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