ViewComponent/view_component · error · ViewComponent::DuplicateSlotContentError

It looks like a block was provided after calling `with_conte

Error message

It looks like a block was provided after calling `with_content` on COMPONENT, which means that ViewComponent doesn't know which content to use.

To fix this issue, use either `with_content` or a block.

What it means

A ViewComponent::Slot can receive content two ways: a block captured at render time (@__vc_content_block) or a value set via with_content (@__vc_content_set_by_with_content). When Slot#to_s renders and both are defined, ViewComponent cannot know which content wins and raises ViewComponent::DuplicateSlotContentError. This is by design: content must be unambiguous, so use either with_content or a block — never both on the same slot.

Source

Thrown at lib/view_component/slot.rb:51

    #
    # There's currently 3 different values that may be set, that we can render.
    #
    # If the slot renderable is a component, the string class name of a
    # component, or a function that returns a component, we render that
    # component instance, returning the string.
    #
    # If the slot renderable is a function and returns a string, it's
    # set as `@__vc_content` and is returned directly.
    #
    # If there is no slot renderable, we evaluate the block passed to
    # the slot and return it.
    def to_s
      return @content if defined?(@content)

      view_context = @parent.send(:view_context)

      if defined?(@__vc_content_block) && defined?(@__vc_content_set_by_with_content)
        raise DuplicateSlotContentError.new(self.class.name)
      end

      @content =
        if __vc_component_instance?
          @__vc_component_instance.__vc_original_view_context = @parent.__vc_original_view_context

          if defined?(@__vc_content_block)
            # render_in is faster than `parent.render`
            @__vc_component_instance.render_in(view_context) do |*args|
              @parent.with_captured_virtual_path(@__vc_content_block_virtual_path) do
                @__vc_content_block.call(*args)
              end
            end
          else
            @__vc_component_instance.render_in(view_context)
          end
        elsif defined?(@__vc_content)
          @__vc_content

View on GitHub (pinned to 9f22c36fa7)

Solutions

  1. Pick one mechanism: delete the with_content call and keep the block, or keep with_content and delete the block.
  2. If content is conditional, branch on the same condition for both paths so only one is ever active (set content OR pass block, never both).
  3. Audit wrapper helpers that call with_content internally when callers may also supply a block.

Example fix

# before (ERB)
<%= render(MyComponent.new) do |c| %>
  <%= c.with_header.with_content("Title") do %>
    Block content
  <% end %>
<% end %>

# after (ERB)
<%= render(MyComponent.new) do |c| %>
  <%= c.with_header.with_content("Title") %>
<% end %>
Defensive patterns

Strategy: validation

Validate before calling

# Before setting slot content, pick exactly one mechanism
slot = component.with_header
if slot.respond_to?(:content?) && slot.content?
  # already has block/content: do not call with_content
else
  slot.with_content("Title")
end

Try / catch

begin
  render(MyComponent.new) do |c|
    c.with_header { "Block" }
  end
rescue ViewComponent::DuplicateSlotContentError
  raise "header slot got with_content AND a block; remove one"
end

Prevention

When it happens

Trigger: Calling with_content on a slot and also passing a block: c.with_header.with_content("Title") do ... end; slot setters invoked with both a content value and a block (with_header("Title") { "Block" }); templates that wrap a with_content call in a block-bearing render helper.

Common situations: Refactoring slot content from blocks to with_content (or vice versa) and leaving both paths active; conditional templates that set content in one branch and yield a block in another; helper wrappers that auto-attach with_content while the caller also passes a block.

Related errors


AI-assisted analysis of ViewComponent/view_component@9f22c36fa7 (2026-08-23). Data as JSON: /api/errors/75953f63535f8ea6. Report an issue: GitHub.