Skip to content

Instantly share code, notes, and snippets.

@tcannonfodder
Created October 24, 2025 20:30
Show Gist options
  • Select an option

  • Save tcannonfodder/a67476d4a0f176a2a6d0be712a15d000 to your computer and use it in GitHub Desktop.

Select an option

Save tcannonfodder/a67476d4a0f176a2a6d0be712a15d000 to your computer and use it in GitHub Desktop.
Herb Components draft

A component starts out as an ERB file.

Take text_field.component.erb

<% attr_accessor: :value %>
<input type="text" value="<%= value %>" %>

Herb then compiles this into a class, which can be instantiated and called to render ERB:

# That ERB above is precompiled by Herb and becomes a class
class HerbComponents::TextField
  attr_accessor :value

  def initialize(value:)
    # ...
  end

  def render(&block)
    # Herb's converted ERB output
  end
end

Need to include some modules? herb_mix to the rescue

<% herb_mix(
  include_modules: [ApplicationHelper, CustomHelper, SomeUnrelatedModule]
) %>
<% attr_accessor: :value %>
<input type="text" value="<%= value %>" %>

Dealing with a complex component? You can:

  1. Specify a parent class:
<% herb_mix(
  parent_class: MyComplexPORO
) %>

<%# ERB All the way %>
# That ERB above is precompiled by Herb and becomes a class
class MyComponent < MyComplexPORO
  attr_accessor :value

  def initialize(value:)
    # ...
  end

  def render(&block)
    # Herb's converted ERB output
  end
end
  1. Define the class inside of the component?
<% herb_class do 
  def supports?
    # ...
  end
%>

<%# More ERB %>

semi_complex.component.erb

# That ERB above is precompiled by Herb and becomes a class
class HerbComponents::SemiComplex
  # ...

  def supports?
    # ...
  end

  def render(&block)
    # Herb's converted ERB output
  end
end
render_component(:text_field) => `HerbComponents::TextField.new.render`
render_component(:text_field, value: 1234) => `HerbComponents::TextField.new(value: 1234).render`
render_component(:text_field, value: 1234) {|component| ... } => `HerbComponents::TextField.new(value: 1234).render{ |component| ... }`

Basically: Try to DRY up creating components; since ViewComponents generate a lot of boilerplate (especially with sidecar files). Since Herb's parser gives you a combined HTML + RB tree, you can compile it into the method that's used to render.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment