Skip to main content

View Component wrapping

TL;DR: If you want to use the auto-generated Stimulus controllers and encapsulated styling for your View Components, you need to:

  1. Call enable_wrapper within your component.rb class
  2. Have a single top-level element in your view (if not, a top-level <div> will be created automatically)
  3. Put your top-level styling within a ._base {} block in your component.scss

The Platform team recently released changes to teamshares-rails that allowed almost all the assets for a component to live in the same folder: the Ruby class, Slim template view, Stimulus controller, preview, and stylesheet. In order to do that, we were previously wrapping every component in a <ts-wrapper> tag, which served as a top-level wrapper element for the Stimulus controller and a generated CSS class that would encapsulate the component’s style rules. However, this wrapper element caused some problems in terms of styling, since it added a new level of hierarchy to the DOM. (More details here.)

Our new pattern (as of late January, 2024) is twofold:

1. Opt-in wrapping via enable_wrapper

View Components no longer get wrapped by default. Wrapping is now opt-in via the enable_wrapper class method, called within the component’s class definition:

class SharedUI::Demo::Component < SharedUI::ApplicationComponent
  enable_wrapper

  def initialize(title:)
    super
    @title = title
  end
end

If you don’t include enable_wrapper, the component will not receive the auto-generated Stimulus controller and CSS class from their folder. We plan to eventually enable this for all components again, making it opt-out rather than opt-in, but this is the intermediate step.

2. Automatic application of styles and controller

If you do enable the wrapper, the component will get wrapped with the <ts-wrapper> tag, which will have the generated controller and scoped CSS class applied to it. A component with this code:

component.html.slim

.bar data-controller="some-controller"
  .baz Some text

component.rb

class Foo::Component < ApplicationComponent
  enable_wrapper

  def some_method
    ...
  end
end

Will yield the following raw HTML:

<ts-wrapper class="c-foo" data-controller="components--foo">
  <div class="bar" data-controller="some-controller">
    <div class="baz">Some text content</div>
  </div>
</ts-wrapper>