Components
Slots with attributes
Read time: 4 minutes
In the previous lesson we used the <.hero>
component twice. Let's say that on the home page we would like the title to be more eye-catching. How to pass attributes to slots?
#Really understanding what slots are
To understand what we are about to do first you need to understand how slots work internally. You may have already noticed that since we render slots using @slot_name
assigns this means that slots are nothing more than special assigns. Every @slot_name
is necessarily a list. If you go to your component and use <%= inspect(@slot_name) %>
you will see something like:
[%{inner_block: #Function<2.32079264/2 in IndexLive.render/1>, __slot__: :nome_do_slot}]
Secretly slots are lists of maps that contain a HEEx render function in the __inner_block__
property and the slot name in the __slot__
property. That being said nothing prevents you from using the same slot multiple times on the same component.
<.hero>
<:title class="text-red-500">IndexLive</:title>
<:title class="text-red-500">IndexLive</:title>
<:subtitle>Welcometo my personal website!</:subtitle>
</.hero>
In the example above when inspecting the @title
slot we will see:
[%{inner_block: #Function<2.32079264/2 in IndexLive.render/1>, __slot__: :title},
%{inner_block: #Function<3.32079264/2 in IndexLive.render/1>, __slot__: :title}]
#Rendering attributes with slots
Why did we go through this whole brainstorming session about understanding that slots are map lists in Elixir if our goal is to render slot classes? Simple: if slots are lists we can do loops and if each slot is a map, we can get properties from them!
Mix.install([
{:liveview_playground, "~> 0.1.5"}
])
defmodule CustomRouter do
use LiveviewPlaygroundWeb, :router
pipeline :browser do
plug :accepts, ["html"]
end
scope "/" do
pipe_through :browser
live "/", IndexLive, :index
live "/other", OtherPageLive, :index
end
end
defmodule CoreComponents do
use LiveviewPlaygroundWeb, :html
slot :title do
attr :class, :string
end
slot :subtitle
slot :inner_block
def hero(assigns) do
~H"""
<div class="bg-gray-800 text-white py-20">
<div class="container mx-auto text-center">
<h1 :for={title_slot <- @title} class={["text-4xl font-bold", Map.get(title_slot, :class)]}>
<%= render_slot(title_slot) %>
</h1>
<p class="mt-4 text-lg"><%= render_slot(@subtitle) %></p>
<%= render_slot(@inner_block) %>
</div>
</div>
"""
end
end
defmodule IndexLive do
use LiveviewPlaygroundWeb, :live_view
import CoreComponents
def render(assigns) do
~H"""
<.hero>
<:title class="text-red-500">IndexLive</:title>
<:subtitle>Welcometo my personal website!</:subtitle>
<.link
class="mt-8 bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded"
navigate={~p"/other"}
>
Get Started
</.link>
</.hero>
<.link navigate={~p"/other"}>Go to other</.link>
"""
end
end
defmodule OtherPageLive do
use LiveviewPlaygroundWeb, :live_view
import CoreComponents
def render(assigns) do
~H"""
<.hero>
<:title>OtherPageLive</:title>
<:subtitle>You're on the first step!</:subtitle>
</.hero>
<.link navigate={~p"/"}>Go to home</.link>
"""
end
end
LiveviewPlayground.start(router: CustomRouter, scripts: ["https://cdn.tailwindcss.com"])
Our slot has gained something new in its definition! Using a do
block we can declare which attributes are important to that specific slot. In this case, just class
.
Additionally we changed the way we render the slot to use a :for={title_slot <- @title}
loop so that we can look at each use of <:title>
individually to get its classes. Inside the class
attribute we use a list to be able to apply the optional attributes that we can extract using Map.get(title_slot, :class)
(which will be nil
by default, resulting in no class being applied). Finally, inside our loop we modify the use of render_slot/2
so that it uses the current loop variable <%= render_slot(title_slot) %>
.
Great! Now your slots can have attributes in them. We managed to solve the original problem: on the home page we would like the slot title to have a different attribute than the other page!
#Recap!
- Each slot is actually a list of map assign.
-
Slots can be given attributes and we can document this using
slot/2
with ado
block. -
To access slot attributes we need to loop through
@slot_name
then useMap.get(loop_item, :attribute_name)
.
Feedback
Got any feedback about this page? Let us know!