Contributing: Adding a new component

Last updated on
3 April 2026

The NYSDS is always adding new components. A big part of maintaining this module will include adding new Drupal components to register these at run time.

Review the component's documentation

Go to the NYSDS website and review the details of this component. Make note of the following:

  • The component's properties. These will need to be referenced in the Drupal component's .yml file and .twig template.
  • Any slots the component has. These slots might need to be coded in the .twig file. You may also need to add an additional prop to the Drupal component to manage the HTML or render entity being passed into the slot.
  • Any dependencies the component has on other components. These will need to be added to the Drupal component's .yml definition file.
  • Any additional new sub-components this component has built into it. You will need to create an additional Drupal component to manage these.

Add the dependency

As described on the documentation for Upgrading the NYSDS in this module, you will first have to start by adding the component to the ./assets/package.json file and then running npm install from the   ./assets folder.

Build the javascript

After adding the dependency, create a new javascript wrapper for the component in the   ./assets/src folder using the component's package name as the file name.

For example, let's say we have an imaginaary component named "example". The ./assets/src/example.js file would look like this:

// src/example.js
import '@nysds/nys-example';

Note that all of the code in the js wrappers look just like this. All it does is include each component in the project and allows the project to easily save each component's actual built javascript in the correct location.

After you have created the wrapper, you can run npm run build from the  ./assets folder. You will see that there is a new file in the project: ./components/example/example.js. You may also find that there is new code in the  ./components/shared folder.  

Commit these changes.

Create the definition file

Every Drupal component needs a definition file. This file documents the component and facilitates the component to be extended and/or overridden by a developer's theme. The file should be named like this: ./components/example/example.component.yml.

The definition file needs to include a schema, name, status, description, and props.

Drupal's internal logic will actually automatically create a library for the javascript file you placed in this folder, however it won't add it as a "module" type which is just a confusing way to say that it can't have any includes in the js. We are including lit code so we need to override the library to alter the js as a "module".

In addition, any other component dependencies need to be declared under the library overrides. Let's pretend our example component relies on the button component and the icon component. Note that the components that are automatically discovered by Drupal follow the same format as described in the example below.

$schema: https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json

name: NYSDS Example
status: stable
description: >
  https://designsystem.ny.gov/components/example/
  The <nys-example> is a reusable web component for use in New York State digital products. It is just an example.

libraryOverrides:
  js:
    example.js: { attributes: { type: 'module' } }
  dependencies:
    - core/components.nys_ds--icon
    - core/components.nys_ds--button

props:
  type: object

If your component has no props, then use the format above to create an empty props object on the yml file. However, it's more likely your component will have props. You will enmerate those underneath that setting. You can review other .component.yml files. If you are creating new props just for Drupal's use, don't forget to add those here. Let's pretend our component has the following props coming from the NYSDS: id (string), title (string), visible (boolean), and icon-size (string with specific options). Let's say it also has a slot where you can add any HTML you want. We will need to add a new prop for the Drupal component to manage that slot.

props:
  type: object
  properties:

    id:
      type: string
      title: ID
      description: Unique identifier for the component instance.

    title:
      type: string
      title: Title
      description: The text that's rendered at the top of the example.

    visible:
      type: boolean
      title: Visible
      description: Whether or not our example is displayed or not (controlled by a button)

    icon-size:
      type: string
      title: Icon size
      description: The size of the icon displayed next to the title.
      enum:
        - xs
        - sm
        - md
        - lg
        - xl

    markup:
      type: string (HTML)
      title: Markup
      description: The HTML content wrapped by the example component.

Create the template file

Every component needs a .twig file in order to render the new web component. Here, we enumerate the props again and then actually write out the twig file. The prop that's being set has to match a prop in the actual NYSDS component. The variable coming into the template can be anything, but we try to make these match the component as well.

Any new props you create need to, instead, be rendered literally on the template. Most of the time, this represents HTML, a Drupal twig render array, or more components. In all three cases, twig is more than capable of handling this kind of input.

{#
/**
 * NYSDS example component template
 * 
 * https://designsystem.ny.gov/components/example
 * 
 * Properties:
 * - id: string
 * - title: string
 * - visible: boolean
 * - icon-size: string
 * - markup: string (render array or HTML)
 *
 */
#}

<nys-example
  {% if id %} id="{{ id }}" {% endif %}
  {% if title %} title="{{ title }}" {% endif %}
  {% if iconSize %} iconSize="{{ iconSize }}" {% endif %}
  {% if visible %} visible {% visible %}
>
  {% if markup %} 
    {{ markup }}
  {% endif %}
</nys-example>

Note that props with key/value pairs are added to the template differently from boolean props.

Some components do have specific slots that content needs to be forwarded to. In these cases, follow the examples laid out by the NYSDS documentation site to code the Drupal template. For example, the modal component has a slot for the modal actions buttons. Those are added to the template thusly:

{% if actions %}
  <div slot="actionButtons">
    {{ actions }}
  </div>
{% endif %}

Once you do all of this, you should be ready to commit your changes and push them up to your fork for testing. 

Sub-components 

A component from the NYSDS can actually contain more than one web component. The term "sub-components" can be a bit of a misnomer because it could refer to either a child component or it could refer to the wrapper component. This can be all rather confusing, but you can tell the primary component by its name. The primary component is always the one that is named exactly the same as the component's js file.

You can see the components' relationships not only through the NYSDS documentation but also in the component's ./components/example/[component name].js file. The js files in here have not been minified. This makes them easy for developers to read and evaluate.  

In these cases, we still create a Drupal component for the sub-component, including both a .yml file and a .twig file, but no new javascript. Instead, the sub-component's .yml file simply lists the main component as a dependency.

Examples of components whose wrappers are the sub-component are checkbox and radiobutton. Their sub-component wrappers are called checkboxgroup and radiogroup.

Examples of components whose children are the sub-component are accordion and dropdownmenu. Their children are called accordionitem and dropdownmenuitem.

We recommend looking at those components to see how they were created to resolve these situations.

Note that this only relates to new components exclusive to this component. Sometimes, existing components (such as button) are used and they don't need a new definition. In these cases, we just use the existing component. The globalheader component is a good example of this; check out that component's .twig file to see how that was done.

Custom Javascript

Very rarely, an NYSDS component will call for the developer to create their own javascript to manage behavior on the component. Much of the time, this javascript will be added to a specific site's libraries instead of in this module. Very rarely, however, it can be added here if the behavior is generic enough that a specific action seems unlikely. In these cases, a custom js file can be added to the Drupal components folder alongside the other .js. This custom file will need to be referenced in the library overrides in the .yml file.

See the modal component for an example of when this is appropriate and how it was done with the custom  modal.actions.js.

libraryOverrides:
  js:
    modal.js: { attributes: { type: 'module' } }
    modal.actions.js: {}
  dependencies:
    - core/components.nys_ds--button

Help improve this page

Page status: No known problems

You can: