Skip to content

End-to-End Component Creation

When to Use

Follow this walkthrough when creating any new Plus Suite component from scratch. Use the checklist at the end to verify completeness.

Decision

Feature Required? Effort
Block content type + fields Yes Low
Sample value generators Yes (for good UX) Low
Promoted block + icon Recommended Low
Block properties Optional Medium
Custom template Recommended Low
Custom sample value plugin Optional (branded content) Medium
Layout plugin (for sections) Optional Medium
Custom tool plugin Rare High

Pattern

Component checklist — verify all before shipping:

  • [ ] Block content type created with appropriate fields
  • [ ] Sample value generators configured for all fields
  • [ ] Block promoted with custom SVG icon (40x40px)
  • [ ] Event subscriber for PlaceBlockEvent (defaults) and BlockPropertiesEvent (design options)
  • [ ] Edit+ handle types configured per field
  • [ ] Block template created with design variant support
  • [ ] CSS styles cover all design variants
  • [ ] Inline editing works for all editable fields
  • [ ] Sample content appears on first placement
  • [ ] Design options update preview immediately (data-auto-submit)

Module structure for a companion module:

# my_theme_plus.services.yml
services:
  my_theme_plus.testimonial_properties:
    class: Drupal\my_theme_plus\EventSubscriber\TestimonialProperties
    tags:
      - { name: event_subscriber }

Event subscriber (condensed):

class TestimonialProperties implements EventSubscriberInterface {

  public static function getSubscribedEvents(): array {
    return [
      PlaceBlockEvent::class => 'onPlaceBlock',
      BlockPropertiesEvent::class => 'onBlockProperties',
    ];
  }

  public function onPlaceBlock(PlaceBlockEvent $event): void {
    $block = $event->getBlockContent();
    if (!$block || $block->bundle() !== 'testimonial') return;
    $block->set('field_rating', 5);
  }

  public function onBlockProperties(BlockPropertiesEvent $event): void {
    $block = $event->getBlockContent();
    if (!$block || $block->bundle() !== 'testimonial') return;
    $event->addBlockProperty([
      '#type' => 'select',
      '#title' => t('Style'),
      '#options' => ['card' => 'Card', 'quote' => 'Large Quote'],
      '#default_value' => $event->getInlineBlockConfiguration()['style'] ?? 'card',
      '#attributes' => ['data-auto-submit' => 'true'],
    ]);
  }
}

Block template using configuration:

{# block--inline-block--testimonial.html.twig #}
{% set style = configuration.testimonial_style|default('card') %}
<div{{ attributes.addClass('testimonial', 'testimonial--' ~ style) }}>
  <blockquote class="testimonial__quote">{{ content.field_quote }}</blockquote>
  <cite class="testimonial__author">{{ content.field_author }}</cite>
</div>

Common Mistakes

  • Wrong: Skipping sample value generators → Right: Empty blocks on placement defeat the purpose of Plus Suite's "Drag. Drop. Done." philosophy
  • Wrong: Block properties without data-auto-submitRight: Users expect instant preview; manual save creates poor UX
  • Wrong: Forgetting to test inline editing on every field → Right: Handle type mismatches cause JS to target wrong elements

See Also