How to render a form in custom template in Drupal 8 and 9

Here we are going to discuss how to render custom form in a twig template.
Here first we are rendering entire form in a twig template. And then rendering each field by field in twig template.
Here we have a module dn_studentslist module.
See below steps for creating a form and rendering form in a twig template.
Step 1
Create below rout in dn_studentslist.routing.yml file and map to the form StudentForm.
dn_studentslist.add_student:
path: '/add/students'
defaults:
_title: 'Add Students'
_form: '\Drupal\dn_studentslist\Form\StudentForm'
requirements:
_access: 'TRUE'
Step 2
Create a form in module path \Src\Form\StudentForm.php.
<?php
namespace Drupal\dn_studentslist\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Database\Database;
use Drupal\Core\Url;
use Drupal\Core\Routing;
/**
* Provides the form for adding countries.
*/
class StudentForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'dn_student_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['fname'] = [
'#type' => 'textfield',
'#title' => $this->t('First Name'),
'#required' => TRUE,
'#maxlength' => 20,
'#default_value' => '',
];
$form['sname'] = [
'#type' => 'textfield',
'#title' => $this->t('Second Name'),
'#required' => TRUE,
'#maxlength' => 20,
'#default_value' => '',
];
$form['age'] = [
'#type' => 'textfield',
'#title' => $this->t('Age'),
'#required' => TRUE,
'#maxlength' => 20,
'#default_value' => '',
];
$form['marks'] = [
'#type' => 'textfield',
'#title' => $this->t('Marks'),
'#required' => TRUE,
'#maxlength' => 20,
'#default_value' => '',
];
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = [
'#type' => 'submit',
'#button_type' => 'primary',
'#default_value' => $this->t('Save') ,
];
$form['#theme'] = 'students_add_form';
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array & $form, FormStateInterface $form_state) {
//print_r($form_state->getValues());exit;
}
/**
* {@inheritdoc}
*/
public function submitForm(array & $form, FormStateInterface $form_state) {
$get_path_update = explode("/", \Drupal::service('path.current')->getPath());
$field = $form_state->getValues();
$re_url = Url::fromRoute('dn_studentslist.add_student');
$fields["fname"] = $field['fname'];
$fields["sname"] = $field['sname'];
$fields["age"] = $field['age'];
$fields["marks"] = $field['marks'];
\Drupal::messenger()->addMessage($this->t('Student data=fname='.$fields["fname"].'=sname='.$fields["sname"].'=age='.$fields["age"].'marks==='.$fields["marks"]));
$form_state->setRedirectUrl($re_url);
}
}
Please note we are passing template name in below line before return form variable.
$form[‘#theme’] = ‘students_add_form’;
In this form we are just submitting student name mark and age and displaying submitted data in same form.
Step 3
Create hook_theme function and provide your form template details in your module file or .theme file as below.
/**
* @file
* Implementing our hooks.
*/
/**
* Implements hook_theme().
*/
function dn_studentslist_theme($existing, $type, $theme, $path) {
return [
'students_list' => [
'variables' => ['students' => [], 'title' => ''],
],
'students_add_form' => [
'render element' => 'form',
],
];
}
Here students_add_form is the form twig template here, students__list is another template.
If you have only one form template, hook_theme return be provided as below.
return [
'students_add_form' => [
'render element' => 'form',
],
];
Step 4
Create a twig template as below in path /templates/
Here template name will be students-add-form.html.twig
Below code in twig prints entire form in above twig template.
<div class="row custom-form">
<div class="col-md-6">
{{ form }}
</div>
</div>
After submission you will get below message in twig template.

You cans see custom-form class in div while inspecting this page.
Step 5
Here we are printing each form field in twig template
So in our students-add-form.html.twig file, first we have to print form hidden fields.
So print below variables first.
{{ form.form_build_id }}
{{ form.form_token }}
{{ form.form_id }}
If we are not printing this , save button of the form will not work.
Next, print each fields in form as below.
{{ form.fname }}
{{ form.sname }}
{{ form.age }}
{{ form.marks }}
{{ form.actions.submit }}
So finaly code in twig template will be as below.
{{ form.form_build_id }}
{{ form.form_token }}
{{ form.form_id }}
<div class="row custom-form">
<div class="col-md-6">
</div>
<div class="col-md-6">
{{ form.fname }}
{{ form.sname }}
{{ form.age }}
{{ form.marks }}
{{ form.actions.submit }}
</div>
</div>
In our StudentForm.php, we have submit button inside $form[‘actions’] . so we have to use {{ form.actions.submit }} in order to print submit button.
Now we can access the page /add/students

So here submit action is returning success message with all details.
Download sample source code here.