diff --git a/README.md b/README.md
index c16389d..9cd73fb 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@ Stack is a simple card-style Hugo theme designed for bloggers, some of its featu
 - No CSS framework, keep it simple and minimal
 - Properly cropped thumbnails
 - Subsection support
+- Table of contents
 
 ## Requirements
 
diff --git a/assets/scss/grid.scss b/assets/scss/grid.scss
index c0dc9fc..a0eddca 100644
--- a/assets/scss/grid.scss
+++ b/assets/scss/grid.scss
@@ -92,7 +92,6 @@
 
 main.main {
     min-width: 0;
-    padding: 0 15px;
     max-width: 100%;
     flex-grow: 1;
     padding-top: var(--main-top-padding);
@@ -101,12 +100,10 @@ main.main {
 .main-container {
     min-height: 100vh;
     align-items: flex-start;
+    padding: 0 15px;
+    column-gap: var(--section-separation);
 
     @include respond(md) {
-        padding: 0 10px;
-    }
-
-    @include respond(lg) {
         padding: 0 20px;
     }
 }
diff --git a/assets/scss/partials/base.scss b/assets/scss/partials/base.scss
index e7aefe6..ab3bf42 100644
--- a/assets/scss/partials/base.scss
+++ b/assets/scss/partials/base.scss
@@ -1,6 +1,7 @@
 html {
     font-size: 62.5%;
     overflow-y: scroll;
+    scroll-behavior: smooth;
 }
 
 * {
diff --git a/assets/scss/partials/layout/article.scss b/assets/scss/partials/layout/article.scss
index 03e8ea1..618ca54 100644
--- a/assets/scss/partials/layout/article.scss
+++ b/assets/scss/partials/layout/article.scss
@@ -114,6 +114,114 @@
     }
 }
 
+.article-page.has-toc {
+    scroll-behavior: smooth;
+
+    .left-sidebar {
+        display: none;
+    }
+
+    .right-sidebar {
+        width: 100%;
+        padding: 0;
+        display: none;
+
+        @include respond(xl) {
+            display: block;
+            top: var(--main-top-padding);
+        }
+    }
+
+    #article-toolbar {
+        display: block;
+
+        @include respond(md) {
+            padding: 0;
+        }
+
+        @include respond(xl) {
+            margin-top: 0;
+            position: sticky;
+            top: var(--main-top-padding);
+            flex-shrink: 1;
+
+            a {
+                background: transparent;
+                box-shadow: none;
+                border: 1px solid var(--body-text-color);
+                width: 100%;
+                margin-right: 0;
+
+                svg {
+                    flex-shrink: 0;
+                }
+            }
+        }
+    }
+
+    .main-container {
+        align-items: start;
+        flex-direction: column;
+
+        @include respond(xl) {
+            flex-direction: row;
+        }
+    }
+
+    .main {
+        padding-top: 0;
+
+        @include respond(xl) {
+            padding-top: var(--main-top-padding);
+        }
+    }
+}
+
+.widget--toc {
+    background-color: var(--card-background);
+    border-radius: var(--card-border-radius);
+    box-shadow: var(--shadow-l1);
+    display: flex;
+    flex-direction: column;
+    color: var(--card-text-color-main);
+
+    #TableOfContents {
+        ol {
+            counter-reset: item;
+            list-style-type: none;
+            padding: 0;
+            margin: 0;
+        }
+
+        & > li {
+            padding: 0;
+            margin: 0;
+        }
+
+        li {
+            margin: 15px 20px;
+            padding: 5px;
+
+            &::before {
+                counter-increment: item;
+                content: counters(item, ".") ". ";
+                font-weight: bold;
+                margin-right: 5px;
+            }
+
+            & > ol {
+                margin-top: 10px;
+                padding-left: 10px;
+                margin-bottom: -5px;
+
+                & > li:last-child {
+                    margin-bottom: 0;
+                }
+            }
+        }
+    }
+}
+
 .related-contents--wrapper {
     margin-bottom: var(--section-separation);
 }
diff --git a/assets/scss/partials/sidebar.scss b/assets/scss/partials/sidebar.scss
index f5abe1e..462729d 100644
--- a/assets/scss/partials/sidebar.scss
+++ b/assets/scss/partials/sidebar.scss
@@ -1,5 +1,4 @@
 .sidebar {
-    padding: 0 15px;
     &.sticky {
         @include respond(md) {
             position: sticky;
@@ -22,8 +21,8 @@
 
     @include respond(md) {
         width: auto;
-        margin-right: 1%;
-        padding: var(--main-top-padding) 15px;
+        padding-top: var(--main-top-padding);
+        padding-bottom: var(--main-top-padding);
         max-height: 100vh;
     }
 
@@ -46,7 +45,6 @@
     }
 
     @include respond(lg) {
-        margin-left: 1%;
         padding-top: var(--main-top-padding);
     }
 }
@@ -55,7 +53,7 @@
     z-index: 1;
     transition: box-shadow 0.5s ease;
 
-    padding: 15px 30px;
+    padding: 15px;
 
     @include respond(md) {
         padding: 0;
diff --git a/exampleSite/config.yaml b/exampleSite/config.yaml
index 51274d6..bf03343 100644
--- a/exampleSite/config.yaml
+++ b/exampleSite/config.yaml
@@ -42,6 +42,7 @@ params:
 
     article:
         math: false
+        toc: true
         license:
             enabled: true
             default: Licensed under CC BY-NC-SA 4.0
@@ -145,5 +146,9 @@ related:
           weight: 200
 
 markup:
+    tableOfContents:
+        endLevel: 4
+        ordered: true
+        startLevel: 2
     highlight:
         noClasses: false
diff --git a/i18n/en.yaml b/i18n/en.yaml
index 99d34ce..21b1310 100644
--- a/i18n/en.yaml
+++ b/i18n/en.yaml
@@ -17,8 +17,15 @@ list:
         other: Subsections
 
 article:
+    back:
+        other: Back
+
+    tableOfContents:
+        other: Table of contents
+
     relatedContents:
         other: Related contents
+
     lastUpdatedOn:
         other: Last updated on
 
@@ -32,8 +39,10 @@ widget:
     archives:
         title:
             other: Archives
+
         more:
             other: More
+
     tagCloud:
         title:
             other: Tags
@@ -41,13 +50,16 @@ widget:
 search:
     title:
         other: Search
+
     placeholder:
         other: Type something...
+
     resultTitle:
         other: "#PAGES_COUNT pages (#TIME_SECONDS seconds)"
 
 footer:
     builtWith:
         other: Built with {{ .Generator }}
+
     designedBy:
         other: Theme {{ .Theme }} designed by {{ .DesignedBy }}
diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html
index ce0ddae..8a5ff95 100644
--- a/layouts/_default/baseof.html
+++ b/layouts/_default/baseof.html
@@ -6,8 +6,10 @@
     </head>
     <body class="{{ block `body-class` . }}{{ end }}">
         {{- partial "head/colorScheme" . -}}
-        <div class="container main-container flex on-phone--column {{ if .Site.Params.widgets.enabled }}extended{{ else }}compact{{ end }} {{ block `container-class` . }}{{end}}">
-            {{ partial "sidebar/left.html" . }}
+        <div class="container main-container flex {{ block `container-class` . }}on-phone--column {{ if .Site.Params.widgets.enabled }}extended{{ else }}compact{{ end }}{{ end }}">
+            {{- block "left-sidebar" . -}}
+                {{ partial "sidebar/left.html" . }}
+            {{- end -}}
             <main class="main full-width">
                 {{- block "main" . }}{{- end }}
             </main>
diff --git a/layouts/_default/single.html b/layouts/_default/single.html
index d9d2f3e..040b547 100644
--- a/layouts/_default/single.html
+++ b/layouts/_default/single.html
@@ -1,7 +1,22 @@
-{{ define "body-class" }}article-page{{ end }}
+{{ define "body-class" }}
+    {{ $TOCEnabled := default (default false .Site.Params.article.toc) .Params.toc }}
+    {{- .Scratch.Set "hasTOC" (and (ge (len .TableOfContents) 100) $TOCEnabled) -}}
+    article-page {{ if (.Scratch.Get "hasTOC") }}has-toc{{ end }}
+{{ end }}
+
+{{ define "container-class" }}
+    {{ if (.Scratch.Get "hasTOC") }}
+        extended
+    {{ else }}
+        on-phone--column {{ if .Site.Params.widgets.enabled }}extended{{ else }}compact{{ end }}
+    {{ end }}
+{{ end }}
+
 {{ define "main" }}
     {{ partial "article/article.html" . }}
 
+    {{ partial "article/components/related-contents" . }}
+     
     {{ if or (not (isset .Params "comments")) (eq .Params.comments "true")}} 
         {{ partial "comments/include" . }}
     {{ end }}
@@ -9,4 +24,34 @@
     {{ partialCached "footer/footer" . }}
 
     {{ partialCached "article/components/photoswipe" . }}
+{{ end }}
+
+{{ define "left-sidebar" }}
+    {{ if (.Scratch.Get "hasTOC") }}
+        <div id="article-toolbar">
+            <a href="{{ .Site.BaseURL }}" class="back-home">
+                {{ (resources.Get "icons/back.svg").Content | safeHTML }}
+                <span>{{ T "article.back" }}</span>
+            </a>
+        </div>
+    {{ else }}
+        {{ partial "sidebar/left.html" . }}
+    {{ end }}
+{{ end }}
+
+{{ define "right-sidebar" }}
+    {{ if (.Scratch.Get "hasTOC") }}
+        <aside class="sidebar right-sidebar sticky">
+            <section class="widget archives">
+                <div class="widget-icon">
+                    {{ partial "helper/icon" "hash" }}
+                </div>
+                <h2 class="widget-title section-title">{{ T "article.tableOfContents" }}</h2>
+                
+                <div class="widget--toc">
+                    {{ .TableOfContents }}
+                </div>
+            </section>
+        </aside>
+    {{ end }}
 {{ end }}
\ No newline at end of file
diff --git a/layouts/post/single.html b/layouts/post/single.html
deleted file mode 100644
index be983ec..0000000
--- a/layouts/post/single.html
+++ /dev/null
@@ -1,21 +0,0 @@
-{{ define "container-class" }}article-page with-toolbar hide-sidebar-sm{{ end }}
-{{ define "main" }}
-    <div id="article-toolbar">
-        <a href="{{ .Site.BaseURL }}" class="back-home">
-            {{ (resources.Get "icons/back.svg").Content | safeHTML }}
-            <span>Back</span>
-        </a>
-    </div>
-
-    {{ partial "article/article.html" . }}
-
-    {{ partial "article/components/related-contents" . }}
-
-    {{ if or (not (isset .Params "comments")) (eq .Params.comments "true")}}
-        {{ partial "comments/include" . }}
-    {{ end }}
-
-    {{ partialCached "footer/footer" . }}
-
-    {{- partialCached "article/components/photoswipe.html" . -}}
-{{ end }}
\ No newline at end of file