The principle is to make an upper layer in the form template which will be called by the basic form and rendered in replacement of it.
Let's see how to do that for login and registration forms :
First, you'll have to implement a theme hook to tell that you want to make an upper layer for the form.
In your .theme file just insert :
/**
* Implements hook_theme().
*/
function THEMENAME_theme() {
return array(
'user_login' => array('render element' => 'form'),
'user_pass' => array('render element' => 'form'),
);
}
So, we basically tell to Drupal that when rendering user_login and user_pass form it has to wrap elements into $form variable which will make our upper layer.
Next step is to tell Drupal that it has to look in a new template file for the respective forms.
In your .theme file :
/**
* Implements hook_form_FORM_ID_alter().
*/
function THEMENAME_form_user_login_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
$form['#theme'] = 'user_login';
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function THEMENAME_form_user_pass_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
$form['#theme'] = 'user_pass';
}
We just alter the forms to make them load a new template file when called.
And voilà, now you just have to make two templates files with the name you gave them, here :
user-login.html.twig & user-pass.html.twig
Note that underscores are translated into hyphens in file names.
Let's see what's inside user-login.html.twig :
{#
/**
* Exemple of twig template for user-login
*/
#}
<form{{ attributes }}>
<div class="account-input-wrapper">
<div>{{ form.name }}</div>
<div>{{ form.pass }}</div>
{{ form|without('name', 'pass') }}
</div>
</form>
As you can see, the trick reside in calling the entire form at the end of the file without the values you called. The aim is to print all validation fields needed by drupal. If you don't do that you'll be likely to get an error when submiting the form.
This methodology can be applied to any other form. You'll be able to get your field with {{ form.field_name }}. Don't forget to print the entire form without the value you called manually.
If you want a more general solution and get rid of customs templates implementations, you can also implement theme suggestions on form ID as is:
/**
* Implements theme_theme_suggestions_form_alter().
*/
function THEMENAME_theme_suggestions_form_alter(array &$suggestions, array $variables) {
$suggestions[] = 'form__' . str_replace('-','_', $variables['element']['#id']);;
}
This will tell Drupal to look into your template folder for files with name form--form-id.html.twig. Then here you'll just need to create two templates files called form--user-login.form.html.twig and form--user-register-form.html.twig in your template file.