How to create custom twig template and render data in custom twig file in Drupal 8 and 9

Here we are going to create a twig template in our custom module and loading data in that twig file with controller.
Here dn_studentslist is our custom module name.
Step 1
Create below rout in dn_studentslist.routing.yml file.
dn_studentslist.student:
path: '/list/students'
defaults:
_title: 'Students'
_controller: '\Drupal\dn_studentslist\Controller\StudentController::listStudents'
requirements:
_access: 'TRUE'
Step 2
Create below theme hook in order to define template name and variables in twig files.
function dn_studentslist_theme($existing, $type, $theme, $path) {
return [
'students_list' => [
'variables' => ['students' => [], 'title' => ''],
]
];
}
Here students_list is the template name. students and title are variables. you have to create students-list.twig.html files in templates folder.
Step 3
Here we are populating the variables in controller.
class StudentController extends ControllerBase {
/**
* {@inheritdoc}
*/
public function listStudents() {
$students = [
['name' => 'Eddy'],
['name' => 'Nadeem'],
['name' => 'Ramesh'],
['name' => 'Adam'],
['name' => 'Shana']
];
return [
'#theme' => 'students_list',
'#items' => $students,
'#title' => $this->t('All students'),
];
}
As you can see above, variables are populated and returns with template name.
Step 4
Create below file in module template folder.
\templates\students-list.html.twig
provide below content. Here we are displaying students details by iterating through students object.
<div class="table-horizontal-container">
<table class="unfixed-table">
<thead>
<tr><th>Name</th><th>Age</th></tr>
</thead>
<tbody>
{% for student in students %}
<tr><th>{{ student.name }}</th><td>{{ student.age }}</td></tr>
{% endfor %}
</tbody>
</table>
So final table will look like as below.

Step 5
For adding style to your html table, create below file.
dn_studentslist.libraries.yml
add below to this file.
table-style:
css:
theme:
css/style.css: {}
Create a css folder and place below content in style.css.
/* default styling. Nothing to do with freexing first row and column */
main {display: flex;}
main > * {border: 1px solid;}
table {border-collapse: collapse; font-family: helvetica}
td, th {border: 1px solid;
padding: 10px;
min-width: 200px;
background: white;
box-sizing: border-box;
text-align: left;
}
.table-container {
position: relative;
max-height: 300px;
width: 500px;
overflow: scroll;
}
thead th {
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 2;
background: hsl(20, 50%, 70%);
}
thead th:first-child {
left: 0;
z-index: 3;
}
tfoot {
position: -webkit-sticky;
bottom: 0;
z-index: 2;
}
tfoot td {
position: sticky;
bottom: 0;
z-index: 2;
background: hsl(20, 50%, 70%);
}
tfoot td:first-child {
z-index: 3;
}
tbody {
overflow: scroll;
height: 200px;
}
/* MAKE LEFT COLUMN FIXEZ */
tr > :first-child {
position: -webkit-sticky;
position: sticky;
background: hsl(180, 50%, 70%);
left: 0;
}
/* don't do this */
tr > :first-child {
box-shadow: inset 0px 1px black;
}
And now update twig template, students-list.html.twig as below. Here we are attaching css library in order to import style.css
{{ attach_library('dn_studentslist/table-style') }}
<h4>{{ title }}</h4>
<div class="table-container">
<div class="table-horizontal-container">
<table class="unfixed-table">
<thead>
<tr><th>Name</th><th>Age</th></tr>
</thead>
<tbody>
{% for student in students %}
<tr><th>{{ student.name }}</th><td>{{ student.age }}</td></tr>
{% endfor %}
</tbody>
</table>
Clear the cache and access the page /list/students.
Table will be look like as below.

Download sample source code here.