Can an AJAX form request replace elements of different types? I'm trying to write an AJAX form in Drupal 8. I managed to get a simple example working yesterday, but today, my work fails without error. I was wondering whether anyone here could spot my error.

Here are the relevant excerpts from my OverviewForm:

public function buildForm(array $form, FormStateInterface $form_state) {

  ...

  $form['job_id'] = [
    '#weight' =&gt; 1,
    '#title' =&gt; 'Job',
    '#type' =&gt; 'select',
    '#options' =&gt; $formatted_jobs,
    '#ajax' =&gt; [
      'callback' =&gt; '::showFiles',
      'event' =&gt; 'change',
      'wrappper' =&gt; 'jobfiles'
    ]
  ];

  $form['jobfiles'] = [
    '#weight' =&gt; 2,
    '#title' =&gt; 'Files',
    '#type' =&gt; 'html_tag',
    '#tag' =&gt; 'span',
    '#value' =&gt; 'Please make a selection above',
    '#attributes' =&gt; [
      'id' =&gt; ['jobfiles']
    ] 
  ];

  return $form;
}

public function showFiles(array &amp;$form, FormStateInterface $form_state) {
  $job_id = $form_state-&gt;getValue('job_id');
  $elem = [
    '#weight' =&gt; 2,
    '#type' =&gt; 'item_list',
    '#items' =&gt; $this-&gt;_files($job_id),
    '#attributes' =&gt; [
      'id' =&gt; ['jobfiles']
    ]
  ];

  return $elem;
}


The list of jobs populates correctly, but when I select one, the initial jobfiles span isn't replaced with anything. I'd expect it to at least disappear when I select a value from the select. Thanks in advance!
I should try using #prefix/#suffix for wrapper instead the element itself. Also IMHO it's not a good idea mess around with the elements you send during build. Take in mind the the build is done every time you render the form, even for AJAX calls. A possible solution could be hide the second element using Form States API and put the Please make selection in your prior element.

public function buildForm(array $form, FormStateInterface $form_state) {
  // Code excerpt.
  $form['job_id'] = [
    '#weight' =&gt; 1,
    '#title' =&gt; t('Job'),
    '#type' =&gt; 'select',
    '#empty_value' =&gt; t('Please select a job'),
    '#options' =&gt; $formatted_jobs,
    '#ajax' =&gt; [
      'callback' =&gt; [get_class($this), 'showFiles'],
      'wrapper' =&gt; 'ajax-wrapper',
      'event' =&gt; 'change',
    ],
    '#required' =&gt; TRUE,
  ];

  // Render a hidden and empty list.
  $form['jobfiles'] = [
    '#weight' =&gt; 2,
    '#title' =&gt; t('Files'),
    '#type' =&gt; 'item_list',
    '#items' =&gt; [],
    '#prefix' =&gt; '&lt;div id="ajax-wrapper"&gt;',
    '#suffix' =&gt; '&lt;/div&gt;',
    '#attributes' =&gt; [
      'class' =&gt; ['invisible']
    ]
  ];

  return $form;
}

public function showFiles(array &amp;$form, FormStateInterface $form_state) {
  $job_id = $form_state-&gt;getValue('job_id');
  $form['jobfiles']['#items'] = $this-&gt;_files((int)$job_id);
  // Remove the 'invisible' class.
  $form['jobfiles']['#attributes']['class'] = array_filter($form['jobfiles']['#attributes']['class'], function($v) {
    return $v != 'invisible';
  });
  // You could also return the element instead.
  $response = new AjaxResponse();
  $response-&gt;addCommand(new ReplaceCommand('#ajax-wrapper', [$form['jobfiles']]));

  return $response;
}


Hope this may help you.

Update:
Replace $element with $form. Also worth checking the answer provided by @joshua-moore as it is valid too.