Back to all examples

Dynamically add an item to a dropdown list

type Task = {
  value: string;
  label: string;
  mount: GoabDropdownItemMountType;
};

const DEFAULT_TASKS: Task[] = [
    { label: "Finish Report", value: "finish-report", mount: "append" },
    { label: "Attend Meeting", value: "attend-meeting", mount: "append" },
    { label: "Reply Emails", value: "reply-emails", mount: "append" },
  ];

  const [tasks, setTasks] = useState<Task[]>(DEFAULT_TASKS);
  const [newTask, setNewTask] = useState<string>("");
  const [mountType, setMountType] = useState<string>("append");
  const [selectedTask, setSelectedTask] = useState<string>("");
  const [taskError, setTaskError] = useState<boolean>(false);
  const [isReset, setIsReset] = useState<boolean>(false);

  function onMountTypeChange(value: string | undefined) {
    setMountType(value as string);
  }

  function addTask() {
    if (newTask === "") {
      setTaskError(true);
      return;
    }
    setTaskError(false);

    const task: Task = {
      label: newTask,
      value: newTask.toLowerCase().replace(" ", "-"),
      mount: mountType as GoabDropdownItemMountType,
    };
    setTasks([...tasks, task]);
    setNewTask("");
    setIsReset(false);
  }

  function reset() {
    setTasks(DEFAULT_TASKS);
    setMountType("append");
    setNewTask("");
    setSelectedTask("");
    setTaskError(false);
    setIsReset(true);
  }
<GoabxFormItem
          requirement="required"
          label="Name of item"
          error={taskError ? "Please enter item name" : undefined}
          helpText="Add an item to the dropdown list below">
          <GoabxInput
            onChange={(event: GoabInputOnChangeDetail) => setNewTask(event.value)}
            name="item"
            value={newTask}
          />
        </GoabxFormItem>

        <GoabxFormItem mt="l" label="Add to">
          <GoabxRadioGroup
            name="mountType"
            onChange={(event: GoabRadioGroupOnChangeDetail) => onMountTypeChange(event.value)}
            value={mountType}
            orientation="horizontal">
            <GoabxRadioItem value="prepend" label="Start" />
            <GoabxRadioItem value="append" label="End" />
          </GoabxRadioGroup>
        </GoabxFormItem>

        <GoabButtonGroup alignment="start" gap="relaxed" mt="l">
          <GoabxButton type="primary" onClick={addTask}>
            Add new item
          </GoabxButton>
          <GoabxButton type="tertiary" onClick={reset}>
            Reset list
          </GoabxButton>
        </GoabButtonGroup>

        <GoabDivider mt="l" />

        <GoabxFormItem mt="l" label="All items">
          <div style={{ width: isReset ? "320px" : "auto" }}>
            <GoabxDropdown
              key={tasks.length}
              onChange={(event: GoabDropdownOnChangeDetail) =>
                setSelectedTask(event.value as string)
              }
              value={selectedTask}
              name="selectedTask">
              {tasks.map(task => (
                <GoabxDropdownItem
                  key={task.value}
                  value={task.value}
                  mountType={task.mount}
                  label={task.label}
                />
              ))}
            </GoabxDropdown>
          </div>
      </GoabxFormItem>
defaultTasks: Task[] = [
    { label: "Finish Report", value: "finish-report", mount: "append" },
    { label: "Attend Meeting", value: "attend-meeting", mount: "append" },
    { label: "Reply Emails", value: "reply-emails", mount: "append" },
  ];

  tasks: Task[] = [...this.defaultTasks];
  newTask = "";
  mountType: GoabDropdownItemMountType = "append";
  selectedTask = "";
  taskError = false;
  renderTrigger = true;

  onMountTypeChange(event: GoabRadioGroupOnChangeDetail): void {
    this.mountType = event.value as GoabDropdownItemMountType;
  }

  onNewTaskChange(event: GoabInputOnChangeDetail): void {
    this.newTask = event.value;
    this.taskError = false;
  }

  onSelectedTaskChange(event: GoabDropdownOnChangeDetail): void {
    this.selectedTask = event.value as string;
  }

  addTask(): void {
    if (this.newTask === "") {
      this.taskError = true;
      return;
    }
    this.taskError = false;

    const task: Task = {
      label: this.newTask,
      value: this.newTask.toLowerCase().replace(" ", "-"),
      mount: this.mountType,
    };
    this.tasks = this.mountType === "prepend"
      ? [task, ...this.tasks]
      : [...this.tasks, task];
    this.newTask = "";
  }

  reset(): void {
    this.newTask = "";
    this.selectedTask = "";
    this.taskError = false;
    this.tasks = [...this.defaultTasks];
    this.forceRerender();
  }

  forceRerender(): void {
    this.renderTrigger = false;
    setTimeout(() => {
      this.renderTrigger = true;
    }, 0);
  }

  trackByFn(index: number, item: Task): string {
    return item.value;
  }
<goabx-form-item
  requirement="required"
  label="Name of item"
  [error]="taskError ? 'Please enter item name' : undefined"
  helpText="Add an item to the dropdown list below">
  <goabx-input
    name="item"
    [value]="newTask"
    (onChange)="onNewTaskChange($event)">
  </goabx-input>
</goabx-form-item>

<goabx-form-item mt="l" label="Add to">
  <goabx-radio-group
    name="mountType"
    [value]="mountType"
    orientation="horizontal"
    (onChange)="onMountTypeChange($event)">
    <goabx-radio-item value="prepend" label="Start"></goabx-radio-item>
    <goabx-radio-item value="append" label="End"></goabx-radio-item>
  </goabx-radio-group>
</goabx-form-item>

<goab-button-group alignment="start" gap="relaxed" mt="l">
  <goabx-button type="primary" (onClick)="addTask()">
    Add new item
  </goabx-button>
  <goabx-button type="tertiary" (onClick)="reset()">
    Reset list
  </goabx-button>
</goab-button-group>

<goab-divider mt="l"></goab-divider>

<goabx-form-item mt="l" label="All items">
  <ng-container *ngIf="renderTrigger">
    <goabx-dropdown
      [value]="selectedTask"
      name="selectedTask"
      (onChange)="onSelectedTaskChange($event)">
      <goabx-dropdown-item
        *ngFor="let task of tasks; trackBy: trackByFn"
        [value]="task.value"
        [mountType]="task.mount"
        [label]="task.label">
      </goabx-dropdown-item>
    </goabx-dropdown>
  </ng-container>
</goabx-form-item>
const itemInput = document.getElementById('item-input');
const itemFormItem = document.getElementById('item-form-item');
const mountTypeGroup = document.getElementById('mount-type');
const addBtn = document.getElementById('add-btn');
const resetBtn = document.getElementById('reset-btn');
const dropdown = document.getElementById('task-dropdown');

let mountType = 'append';
let newTask = '';

const defaultItems = [
  { value: 'finish-report', label: 'Finish Report' },
  { value: 'attend-meeting', label: 'Attend Meeting' },
  { value: 'reply-emails', label: 'Reply Emails' }
];

mountTypeGroup.addEventListener('_change', (e) => {
  mountType = e.detail.value;
});

itemInput.addEventListener('_change', (e) => {
  newTask = e.detail.value;
  itemFormItem.removeAttribute('error');
});

addBtn.addEventListener('_click', () => {
  if (newTask === '') {
    itemFormItem.setAttribute('error', 'Please enter item name');
    return;
  }

  const newItem = document.createElement('goa-dropdown-item');
  newItem.setAttribute('value', newTask.toLowerCase().replace(' ', '-'));
  newItem.setAttribute('label', newTask);
  newItem.setAttribute('mount', mountType);
  dropdown.appendChild(newItem);

  itemInput.value = '';
  newTask = '';
});

resetBtn.addEventListener('_click', () => {
  dropdown.innerHTML = '';
  defaultItems.forEach(item => {
    const dropdownItem = document.createElement('goa-dropdown-item');
    dropdownItem.setAttribute('value', item.value);
    dropdownItem.setAttribute('label', item.label);
    dropdown.appendChild(dropdownItem);
  });
  itemInput.value = '';
  newTask = '';
  itemFormItem.removeAttribute('error');
});
<goa-form-item version="2"
  id="item-form-item"
  requirement="required"
  label="Name of item"
  helptext="Add an item to the dropdown list below">
  <goa-input version="2" id="item-input" name="item"></goa-input>
</goa-form-item>

<goa-form-item version="2" mt="l" label="Add to">
  <goa-radio-group version="2" id="mount-type" name="mountType" value="append" orientation="horizontal">
    <goa-radio-item value="prepend" label="Start"></goa-radio-item>
    <goa-radio-item value="append" label="End"></goa-radio-item>
  </goa-radio-group>
</goa-form-item>

<goa-button-group alignment="start" gap="relaxed" mt="l">
  <goa-button version="2" id="add-btn" type="primary">Add new item</goa-button>
  <goa-button version="2" id="reset-btn" type="tertiary">Reset list</goa-button>
</goa-button-group>

<goa-divider mt="l"></goa-divider>

<goa-form-item version="2" mt="l" label="All items">
  <goa-dropdown version="2" id="task-dropdown" name="selectedTask">
    <goa-dropdown-item value="finish-report" label="Finish Report"></goa-dropdown-item>
    <goa-dropdown-item value="attend-meeting" label="Attend Meeting"></goa-dropdown-item>
    <goa-dropdown-item value="reply-emails" label="Reply Emails"></goa-dropdown-item>
  </goa-dropdown>
</goa-form-item>

Allow users to add new items to a dropdown list dynamically.

When to use

Use this pattern when:

  • Users need to add custom options to a predefined list
  • The list of options can grow based on user input
  • You want to provide flexibility while maintaining structure

Considerations

  • Use the mountType prop to control where new items appear (prepend or append)
  • Validate input before adding to prevent empty or duplicate entries
  • Provide a reset option to restore the original list
  • Show clear feedback when items are added successfully