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_contentView on GitHub (pinned to 9f22c36fa7)
Solutions
- Pick one mechanism: delete the with_content call and keep the block, or keep with_content and delete the block.
- 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).
- 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
- One content source per slot: choose with_content or a block per call site and never mix.
- When refactoring from blocks to with_content, remove the block in the same change.
- In wrapper helpers, document and assert they own exactly one of the two content paths.
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
- Cannot serialize slot call '#{method_name}' with a block
- Inline templates can only be defined once per-component.
- wrong number of arguments (given #{args.size}, expected 1)
- #{example} is not a valid preview example
- A preview template for example EXAMPLE doesn't exist. To fi
AI-assisted analysis of ViewComponent/view_component@9f22c36fa7 (2026-08-23).
Data as JSON: /api/errors/75953f63535f8ea6.
Report an issue: GitHub.