Guide to Slots in Rails' ViewComponent
Tags: rails, viewcomponent, ruby, webdev
Published: 2024-04-11 18:00:00

This article was previously published at [Rails Designer](https://railsdesigner.com/guide-to-viewcomponent-slots/).

---

ViewComponent, started at GitHub, is a popular open-source project. Since its release, it has garnered significant community support, with hundreds of contributors and over a thousand commits. The goal for it is to break down complex views and improve performance in Rails applications

Slots are one of the most powerful features. From a simple text-output to rendering (multiple other) ViewComponent's. Slots can do it! Let's go over how they work.


## What are slots in ViewComponent?

Slots are a powerful feature of [ViewComponent](https://railsdesigner.com/what-is-view-component/) designed to make components more flexible and reusable by allowing them to accept and render nested content or even other components. Introduced as an improvement in the ["Slots V2" API](https://github.com/ViewComponent/view_component/pull/503), slots are now a default feature of ViewComponent and one I highly recommend you start to use (if you haven't already). Slots are used extensively in [Rails Designer](https://railsdesigner.com/) too.

Let's go over the various types of slots. Yes, slots are versatile. From rendering just some content, to rendering other components to even polymorphic slots.

Slots can be defined in two ways:

- `renders_one`
- `renders_many`

And they do exactly what you think they do. `renders_one` allows you to render (and define) only one slot (think an avatar in UserProfileComponent). `renders_many` can render many slots (think related links in an ArticleComponent). All different kinds of slots, described below, work for both renders one and -many.


## Render content

The most basic version of defining a slot is as follows:

```ruby
# user_profile_component.rb
class UserProfileComponent < ViewComponent::Base
  renders_one :avatar
end
```

```ruby
<⁠%# user_profile_component.html.erb %>
<header>
  <⁠%= avatar %>
  <h1><⁠%= @user.name %></h1>
</header>
```

Then when you render the component in a view you can define the avatar slot as follows:

```ruby
<⁠%= render UserProfileComponent.new do |component| %>
  <⁠% component.with_avatar do %>
    <⁠%= image_tag("path/to/avatar/of/sorts.jpg", alt: "") %>
  <⁠% end %>
<⁠% end %>
```

Little aside: it takes any block, so you could also write `<⁠% component.with_avatar { image_tag("path/to/avatar/of/sorts.jpg", alt: "") } %>`.


## Render another component

There are two ways to render another component through a slot: inside of the component class or as another file. The way they work are similar, but are slightly different set up.


### Render another component inline

```ruby
# user_profile_component.rb
class UserProfileComponent < ViewComponent::Base
  renders_one :avatar, "AvatarComponent"

  class AvatarComponent < ViewComponent::Base
    def call
      tag.img src: "path/to/avatar/of/sorts.jpg", alt: ""
    end
  end
end
```

Notice how the quotes around `AvatarComponent`? Adding the component as a string is how ViewComponent assumes the other component is nested inside the component.


### Render another component from another file

```ruby
# user_profile_component.rb
class UserProfileComponent < ViewComponent::Base
  renders_one :avatar, AvatarComponent
end
```

```ruby
# avatar_component.rb
class AvatarComponent < ViewComponent::Base
  def call
    tag.img src: "path/to/avatar/of/sorts.jpg", alt: ""
  end
end
```

Here the class name (`AvatarComponent`) is referenced, letting ViewComponent to look for the class in another file.


## Render lambda slot

Alright, now this sounds scary! But a lambda is nothing more like a mini-function that you can create on-the-fly without giving it a name. Still afraid? An example probably helps:

```ruby
# user_profile_component.rb
class UserProfileComponent < ViewComponent::Base
  renders_one :avatar, ->(src: nil, alt: alt, css: "w-6 h-6 rounded-full") do
    content_tag(:img, src: src, alt: alt, class: css)
  end
end
```

Then when rendering it:

```ruby
<⁠%= render UserProfileComponent.new do |component| %>
  <⁠% component.with_avatar(src: "path/to/avatar/of/sorts.jpg", alt: "My Profile picture") %>
<⁠% end %>
```

For simple elements like this one, this will work just fine. But you can also call another component. Using both inline and external components as described above.

```ruby
# user_profile_component.rb
class UserProfileComponent < ViewComponent::Base
  renders_one :avatar, ->(src: nil, alt: nil, css: "w-6 h-6 rounded-full") do
    AvatarComponent.new(src: src, alt: alt, css: css)
  end
end
```

This example assumes a `AvatarComponent` to be defined that takes three attributes `src`, `alt` and an optional `css`.


## Render polymorphic components

Polymorphic slots has been a fairly new addition to ViewComponent (since 2.12.0). I personally haven't found too much use for them, but still grabbed for them a few times. Let's look at a, bit of contrived, example:

```ruby
# user_profile_component.rb
class UserProfileComponent < ViewComponent::Base
  renders_one :avatar, types: {
    icon: ->(css: "w-4 h-4 rounded-full") do
      tag.span @user.first.name, class: css
    end,
    image: ->(css: "w-4 h-4 rounded-full") do
      image_tag @user.avatar, class: css
    end
  }
end
```

In your view you can then do this:

```ruby
<⁠%= render UserProfileComponent.new(user: Current.user) do |component| %>
  <⁠% if Current.user.avatar.attached? %>
    <⁠% component.with_image_avatar %>
  <⁠% else %>
    <⁠% component.with_icon_avatar %>
  <⁠% end %>
<⁠% end %>
```

The polymorphic slots are called with the `type` name in them, eg. `with_image_avatar` and `with_icon_avatar`.


## Predicate `slot_name?` methods

Say _what_? Another fancy term, I know. But all it is, is the slot name appended with a question mark. Assume the same user profile component from above.

Let's assume you need to wrap the user's avatar in a parent element. And maybe the avatar slot is optional.

You can then, in your template, wrap the avatar HTML like so to only display it if it's defined:

```ruby
<⁠%# user_profile_component.html.erb %>
<header>
   <⁠% if avatar? %>
    <div class="mr-4">
      <⁠%= avatar %>
     </div>
   <⁠% end %>

  <h1><⁠%= @user.name %></h1>
</header>
```

And that's all you need to know about slots in ViewComponent. The many options might make it sometimes tricky to know which kind of slot to go for, but experimenting—quickly trying an option and move to the next—is the fastest way to get comfortable with the many options around slots.

Have questions around if, when or how to use for a specific example? Feel free to reach out.