Skip to content

hook_name_widget_layouts

Overview

The hook_name_widget_layouts() hook allows modules to provide custom layout options for the name field widget. Layouts control how name components are visually arranged in forms.

Hook Definition

/**
 * Provide widget layout options.
 *
 * @return array
 *   A keyed array of layout settings.
 *   - label: The layout label (required).
 *   - library: An array of libraries to attach to the element.
 *   - wrapper_attributes: An array of wrapper attributes.
 */
function hook_name_widget_layouts() {
  return [
    'inline' => [
      'label' => t('Inline'),
      'library' => [
        'name/widget.inline',
      ],
      'wrapper_attributes' => [
        'class' => ['form--inline', 'clearfix'],
      ],
    ],
  ];
}

Return Structure

The hook should return an associative array where:

  • Key: Machine name of the layout
  • Value: Array with the following keys:
  • label (required) - Translated label for the layout
  • library (optional) - Array of library names to attach
  • wrapper_attributes (optional) - Array of HTML attributes for the wrapper element

Layout Properties

label

The human-readable name displayed in the widget settings.

'label' => t('Stacked'),

library

Drupal libraries to attach to the form element. Used for custom CSS/JavaScript.

'library' => [
  'my_module/name-widget-layout',
  'my_module/inline-forms',
],

Library must be defined in my_module.libraries.yml:

name-widget-layout:
  version: 1.0
  css:
    theme:
      css/name-widget-layout.css: {}
  js:
    js/name-widget-layout.js: {}

wrapper_attributes

HTML attributes applied to the wrapper container.

'wrapper_attributes' => [
  'class' => ['form--inline', 'clearfix', 'name-widget-inline'],
  'data-layout' => 'inline',
],

Complete Example

Custom Module Implementation

/**
 * Implements hook_name_widget_layouts().
 */
function my_module_name_widget_layouts() {
  return [
    // Inline layout - all fields in one row
    'inline' => [
      'label' => t('Inline'),
      'library' => ['my_module/name-inline'],
      'wrapper_attributes' => [
        'class' => ['name-widget-inline', 'clearfix'],
      ],
    ],

    // Two-column layout
    'two_column' => [
      'label' => t('Two Columns'),
      'library' => ['my_module/name-two-column'],
      'wrapper_attributes' => [
        'class' => ['name-widget-two-column', 'layout-columns-2'],
      ],
    ],

    // Compact layout for mobile
    'compact' => [
      'label' => t('Compact (Mobile Friendly)'),
      'library' => ['my_module/name-compact'],
      'wrapper_attributes' => [
        'class' => ['name-widget-compact'],
        'data-responsive' => 'true',
      ],
    ],

    // Grid layout
    'grid' => [
      'label' => t('Grid Layout'),
      'library' => ['my_module/name-grid'],
      'wrapper_attributes' => [
        'class' => ['name-widget-grid', 'layout-grid'],
      ],
    ],
  ];
}

Library Definition (my_module.libraries.yml)

name-inline:
  version: 1.0
  css:
    theme:
      css/name-inline.css: {}
  dependencies:
    - core/drupal

name-two-column:
  version: 1.0
  css:
    theme:
      css/name-two-column.css: {}
  dependencies:
    - core/drupal

name-compact:
  version: 1.0
  css:
    theme:
      css/name-compact.css: {}
  js:
    js/name-compact.js: {}
  dependencies:
    - core/drupal
    - core/drupalSettings

name-grid:
  version: 1.0
  css:
    theme:
      css/name-grid.css: {}
  dependencies:
    - core/drupal

CSS Example (css/name-inline.css)

.name-widget-inline {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.name-widget-inline .form-item {
  flex: 1 1 auto;
  min-width: 150px;
  margin-bottom: 0;
}

.name-widget-inline .form-item--title {
  flex: 0 0 100px;
}

.name-widget-inline .form-item--given,
.name-widget-inline .form-item--family {
  flex: 1 1 200px;
}

.name-widget-inline .form-item--middle {
  flex: 0 0 150px;
}

CSS Example (css/name-two-column.css)

.name-widget-two-column {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  column-gap: 2rem;
}

.name-widget-two-column .form-item--title {
  grid-column: 1;
}

.name-widget-two-column .form-item--given {
  grid-column: 1;
}

.name-widget-two-column .form-item--middle {
  grid-column: 2;
  grid-row: 2;
}

.name-widget-two-column .form-item--family {
  grid-column: 2;
}

Using Custom Layouts

Once defined, custom layouts appear in the field widget settings:

  1. Navigate to: Structure > Content types > [Type] > Manage form display
  2. Click settings for the name field
  3. Select your custom layout from the "Layout" dropdown

Default Layout

The Name module provides a default "stacked" layout. Custom layouts supplement this default:

// Default layout (provided by the Name module)
'default' => [
  'label' => t('Stacked'),
  'library' => [],
  'wrapper_attributes' => [],
],

Layout Discovery

Layouts are discovered when:

  1. The name_widget_layouts() helper function is called
  2. Which invokes hook_name_widget_layouts() on all modules
  3. Results are cached using drupal_static()
// In name.module
function name_widget_layouts() {
  $layouts = &drupal_static(__FUNCTION__);
  if (!isset($layouts)) {
    $layouts = \Drupal::moduleHandler()
      ->invokeAll('name_widget_layouts');
    // Process and cache layouts
  }
  return $layouts;
}

Best Practices

Responsive Design

Make layouts responsive:

'responsive' => [
  'label' => t('Responsive'),
  'library' => ['my_module/name-responsive'],
  'wrapper_attributes' => [
    'class' => ['name-widget-responsive'],
  ],
],
/* Mobile first */
.name-widget-responsive {
  display: block;
}

.name-widget-responsive .form-item {
  margin-bottom: 1rem;
}

/* Tablet and up */
@media (min-width: 768px) {
  .name-widget-responsive {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 1rem;
  }
}

/* Desktop */
@media (min-width: 1024px) {
  .name-widget-responsive {
    grid-template-columns: repeat(3, 1fr);
  }
}

Accessibility

Ensure layouts maintain accessibility:

/* Don't hide labels visually */
.name-widget-compact label {
  /* Good: visually hidden but accessible */
  position: absolute;
  clip: rect(1px, 1px, 1px, 1px);
  overflow: hidden;
}

/* Maintain focus indicators */
.name-widget-inline input:focus {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

RTL Support

Support right-to-left languages:

.name-widget-inline {
  display: flex;
}

[dir="rtl"] .name-widget-inline {
  flex-direction: row-reverse;
}

Testing Custom Layouts

namespace Drupal\Tests\my_module\Functional;

use Drupal\Tests\BrowserTestBase;

class CustomLayoutTest extends BrowserTestBase {

  protected static $modules = ['name', 'my_module'];

  public function testCustomLayoutAvailable() {
    $layouts = name_widget_layouts();
    $this->assertArrayHasKey('inline', $layouts);
    $this->assertEquals('Inline', $layouts['inline']['label']);
  }

  public function testLayoutLibraryAttached() {
    // Create node with name field
    $this->drupalGet('node/add/person');

    // Check that custom library is attached
    $this->assertSession()->responseContains('my_module/name-inline');
  }
}

See Also