diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index 39dbfe2e4710f9b56bdb7bc1d9254c25bbe92065..726c0907c3df66c013e1923fd92892b01eb5e750 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -1,7 +1,7 @@
 class TagsController < ApplicationController
   before_action :authenticate_user!, only: [:edit, :update]
   before_action :set_category, except: [:index]
-  before_action :set_tag, only: [:show, :edit, :update]
+  before_action :set_tag, only: [:show, :edit, :update, :children]
 
   def index
     @tag_set = if params[:tag_set].present?
@@ -24,16 +24,21 @@ class TagsController < ApplicationController
     @tags = if params[:q].present?
               @tag_set.tags.search(params[:q])
             else
-              @tag_set.tags.left_joins(:posts).group(Arel.sql('tags.id')).order(Arel.sql('COUNT(posts.id) DESC'))
-                      .select(Arel.sql('tags.*, COUNT(posts.id) AS post_count'))
-            end
+              @tag_set.tags.order(Arel.sql('COUNT(posts.id) DESC'))
+            end.left_joins(:posts).group(Arel.sql('tags.id')).select(Arel.sql('tags.*, COUNT(posts.id) AS post_count'))
+               .paginate(per_page: 96, page: params[:page])
   end
 
   def show
     sort_params = { activity: { last_activity: :desc }, age: { created_at: :desc }, score: { score: :desc },
                     native: Arel.sql('att_source IS NULL DESC, last_activity DESC') }
     sort_param = sort_params[params[:sort]&.to_sym] || { last_activity: :desc }
-    @posts = @tag.posts.undeleted.where(post_type_id: @category.display_post_types)
+    tag_ids = if params[:self].present?
+                [@tag.id]
+              else
+                @tag.all_children + [@tag.id]
+              end
+    @posts = Post.joins(:tags).where(tags: { id: tag_ids }).undeleted.where(post_type_id: @category.display_post_types)
                  .includes(:post_type, :tags).list_includes.paginate(page: params[:page], per_page: 50)
                  .order(sort_param)
   end
@@ -48,17 +53,19 @@ class TagsController < ApplicationController
     end
   end
 
+  def children
+    @tags = if params[:q].present?
+              @tag.children.search(params[:q])
+            else
+              @tag.children.order(Arel.sql('COUNT(posts.id) DESC'))
+            end.left_joins(:posts).group(Arel.sql('tags.id')).select(Arel.sql('tags.*, COUNT(posts.id) AS post_count'))
+               .paginate(per_page: 96, page: params[:page])
+  end
+
   private
 
   def set_tag
     @tag = Tag.find params[:tag_id]
-    required_ids = @category&.required_tag_ids
-    moderator_ids = @category&.moderator_tag_ids
-    topic_ids = @category&.topic_tag_ids
-    required = required_ids&.include?(@tag.id) ? 'is-filled' : ''
-    topic = topic_ids&.include?(@tag.id) ? 'is-outlined' : ''
-    moderator = moderator_ids&.include?(@tag.id) ? 'is-red is-outlined' : ''
-    @classes = "badge is-tag #{required} #{topic} #{moderator}"
   end
 
   def set_category
@@ -66,6 +73,6 @@ class TagsController < ApplicationController
   end
 
   def tag_params
-    params.require(:tag).permit(:excerpt, :wiki_markdown)
+    params.require(:tag).permit(:excerpt, :wiki_markdown, :parent_id)
   end
 end
diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb
index 24185619b74b7f546f76529149a11dc68d1dc00d..83a063b6040e33d6b5b2545f81c09cdeb18c6b50 100644
--- a/app/helpers/tags_helper.rb
+++ b/app/helpers/tags_helper.rb
@@ -7,4 +7,14 @@ module TagsHelper
          topic_ids.include?(t.id) ? 0 : 1, t.id]
       end
   end
+
+  def tag_classes(tag, category)
+    required_ids = category&.required_tag_ids
+    moderator_ids = category&.moderator_tag_ids
+    topic_ids = category&.topic_tag_ids
+    required = required_ids&.include?(tag.id) ? 'is-filled' : ''
+    topic = topic_ids&.include?(tag.id) ? 'is-outlined' : ''
+    moderator = moderator_ids&.include?(tag.id) ? 'is-red is-outlined' : ''
+    "badge is-tag #{required} #{topic} #{moderator}"
+  end
 end
diff --git a/app/models/tag.rb b/app/models/tag.rb
index efe46842d65bc9e1825082bc8e2018f3f4f574fc..43436d17b89745aa1504585d756244c521e1b904 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -2,7 +2,9 @@ class Tag < ApplicationRecord
   include CommunityRelated
 
   has_and_belongs_to_many :posts
+  has_many :children, class_name: 'Tag', foreign_key: :parent_id
   belongs_to :tag_set
+  belongs_to :parent, class_name: 'Tag', optional: true
 
   validates :excerpt, length: { maximum: 600 }, allow_blank: true
   validates :wiki_markdown, length: { maximum: 30000 }, allow_blank: true
@@ -11,4 +13,10 @@ class Tag < ApplicationRecord
     where('name LIKE ?', "%#{sanitize_sql_like(term)}%")
       .order(sanitize_sql_array(['name LIKE ? DESC, name', "#{sanitize_sql_like(term)}%"]))
   end
+
+  def all_children
+    query = File.read(Rails.root.join('db/scripts/tag_children.sql'))
+    query = query.gsub('$ParentId', id.to_s)
+    ActiveRecord::Base.connection.execute(query).to_a.map(&:first)
+  end
 end
diff --git a/app/views/tags/_list.html.erb b/app/views/tags/_list.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..7b4ffc202edee459079d59a0278e4876177abb1c
--- /dev/null
+++ b/app/views/tags/_list.html.erb
@@ -0,0 +1,13 @@
+<div class="grid">
+  <% required_ids = @category&.required_tag_ids %>
+  <% moderator_ids = @category&.moderator_tag_ids %>
+  <% topic_ids = @category&.topic_tag_ids %>
+
+  <% @tags.each do |tag| %>
+    <% required = required_ids&.include?(tag.id) ? 'is-filled' : '' %>
+    <% topic = topic_ids&.include?(tag.id) ? 'is-outlined' : '' %>
+    <% moderator = moderator_ids&.include?(tag.id) ? 'is-red is-outlined' : '' %>
+    <% classes = "badge is-tag #{required} #{topic} #{moderator}" %>
+    <%= render 'tag', category: @category, tag: tag, classes: classes %>
+  <% end %>
+</div>
\ No newline at end of file
diff --git a/app/views/tags/category.html.erb b/app/views/tags/category.html.erb
index 32f8fd292e9ad58b2709560119b51f8c3a74a4e8..00d461de38226f46eb6b0ad5a55f96a73c8a3566 100644
--- a/app/views/tags/category.html.erb
+++ b/app/views/tags/category.html.erb
@@ -2,16 +2,18 @@
 
 <h1>Tags for <%= @category.name %></h1>
 
-<div class="grid">
-  <% required_ids = @category&.required_tag_ids %>
-  <% moderator_ids = @category&.moderator_tag_ids %>
-  <% topic_ids = @category&.topic_tag_ids %>
+<%= form_tag category_tags_path(@category), method: :get, class: 'form-inline' do %>
+  <div class="form-group-horizontal">
+    <div class="form-group">
+      <%= label_tag :q, 'Search', class: 'form-element' %>
+      <%= text_field_tag :q, params[:q], class: 'form-element' %>
+    </div>
+    <div class="actions has-padding-bottom-1">
+      <button type="submit" class="button is-filled is-medium"><i class="fas fa-search"></i><span class="sr-only">Search</span></button>
+    </div>
+  </div>
+<% end %>
 
-  <% @tags.each do |tag| %>
-    <% required = required_ids&.include?(tag.id) ? 'is-filled' : '' %>
-    <% topic = topic_ids&.include?(tag.id) ? 'is-outlined' : '' %>
-    <% moderator = moderator_ids&.include?(tag.id) ? 'is-red is-outlined' : '' %>
-    <% classes = "badge is-tag #{required} #{topic} #{moderator}" %>
-    <%= render 'tag', category: @category, tag: tag, classes: classes %>
-  <% end %>
-</div>
+<%= render 'list' %>
+
+<%= will_paginate @tags, renderer: BootstrapPagination::Rails %>
diff --git a/app/views/tags/children.html.erb b/app/views/tags/children.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..92cc1e14b5339f1d1df57be9c687bcdac33a358f
--- /dev/null
+++ b/app/views/tags/children.html.erb
@@ -0,0 +1,19 @@
+<% content_for :title, "Child tags of #{@tag.name}" %>
+
+<h1>Child tags of <span class="<%= tag_classes(@tag, @category) %> is-large"><%= @tag.name %></span></h1>
+
+<%= form_tag tag_children_path(id: @category.id, tag_id: @tag.id), method: :get, class: 'form-inline' do %>
+  <div class="form-group-horizontal">
+    <div class="form-group">
+      <%= label_tag :q, 'Search', class: 'form-element' %>
+      <%= text_field_tag :q, params[:q], class: 'form-element' %>
+    </div>
+    <div class="actions has-padding-bottom-1">
+      <button type="submit" class="button is-filled is-medium"><i class="fas fa-search"></i><span class="sr-only">Search</span></button>
+    </div>
+  </div>
+<% end %>
+
+<%= render 'list' %>
+
+<%= will_paginate @tags, renderer: BootstrapPagination::Rails %>
diff --git a/app/views/tags/edit.html.erb b/app/views/tags/edit.html.erb
index 487d673dbf76a22495b9a82ea0c5e9e226ed7dfa..830c7afeff9c223c96191340f3a65533276dcedd 100644
--- a/app/views/tags/edit.html.erb
+++ b/app/views/tags/edit.html.erb
@@ -19,6 +19,17 @@
 <% end %>
 
 <%= form_for @tag, url: update_tag_path(id: @category.id, tag_id: @tag.id) do |f| %>
+  <div class="form-group">
+    <%= f.label :parent_id, 'Parent tag', class: 'form-element' %>
+    <span class="form-caption">
+      Optional. Select a parent tag to make this part of a tag hierarchy.
+    </span>
+    <%= f.select :parent_id, options_for_select(@tag.parent.present? ? [[@tag.parent.name, @tag.parent_id]] : [],
+                                                selected: @tag.parent.present? ? @tag.parent_id : nil),
+                 { include_blank: true }, class: "form-element js-tag-select",
+                 data: { tag_set: @category.tag_set_id, use_ids: true } %>
+  </div>
+
   <div class="form-group">
     <%= f.label :excerpt, 'Usage guidance', class: 'form-element' %>
     <span class="form-caption">
diff --git a/app/views/tags/show.html.erb b/app/views/tags/show.html.erb
index 9f815fda137ee3f61cdc4933c86ebf7ca34b000d..b1b0478f9ed0f162f7230a383d598ca47bd07800 100644
--- a/app/views/tags/show.html.erb
+++ b/app/views/tags/show.html.erb
@@ -1,12 +1,25 @@
 <% content_for :title, "Posts tagged #{@tag.name}" %>
 
-<h1>
-  Posts tagged <span class="<%= @classes %> is-large"><%= @tag.name %></span>
+<h1 class="has-margin-0 has-margin-top-4">
+  Posts tagged <span class="<%= tag_classes(@tag, @category) %> is-large"><%= @tag.name %></span>
 </h1>
+<p class="has-color-tertiary-900 has-font-weight-normal has-margin-0 has-font-family-brand">
+  <% if @tag.parent_id.present? %>
+      Subtag of <%= link_to @tag.parent.name, tag_path(id: @category.id, tag_id: @tag.parent_id),
+                            class: tag_classes(@tag.parent, @category) %>
+  <% end %>
+  <% child_count = @tag.children.count %>
+  <% if @tag.parent_id.present? && child_count > 0 %>
+    &middot;
+  <% end %>
+  <% if child_count > 0 %>
+    <%= link_to pluralize(child_count, 'child tag'), tag_children_path(id: @category.id, tag_id: @tag.id) %>
+  <% end %>
+</p>
 
 <div class="widget">
   <div class="widget--body has-font-size-caption has-color-tertiary-900">
-    <%= raw(sanitize(@tag.excerpt, scrubber: scrubber).gsub("\n", '<br/>')) %>
+    <%= raw(sanitize(@tag.excerpt, scrubber: scrubber)&.gsub("\n", '<br/>')) %>
     <% unless @tag.excerpt.present? %>
       <p class="has-font-size-caption has-margin-0">
         <em>
@@ -45,17 +58,10 @@
   </div>
 
   <div class="button-list is-gutterless has-margin-2">
-    <%= link_to 'Activity', query_url(sort: 'activity'),
-                class: "button is-muted is-outlined #{(params[:sort].nil?) && !current_page?(questions_lottery_path) ||
-                    params[:sort] == 'activity' ? 'is-active' : ''}" %>
-    <%= link_to 'Age', query_url(sort: 'age'),
-                class: "button is-muted is-outlined #{params[:sort] == 'age' ? 'is-active' : ''}" %>
-    <%= link_to 'Score', query_url(sort: 'score'),
-                class: "button is-muted is-outlined #{params[:sort] == 'score' ? 'is-active' : ''}" %>
-    <% if SiteSetting['AllowContentTransfer'] %>
-      <%= link_to 'Native', query_url(sort: 'native'),
-                  class: "button is-muted is-outlined #{params[:sort] == 'native' ? 'is-active' : ''}" %>
-    <% end %>
+    <%= link_to 'Tag Only', query_url(self: 1),
+                class: "button is-muted is-outlined #{params[:self].present? ? 'is-active' : ''}" %>
+    <%= link_to 'Tag + Children', tag_path(id: @category.id, tag_id: @tag.id),
+                class: "button is-muted is-outlined #{params[:self].nil? ? 'is-active' : ''}" %>
   </div>
 </div>
 
diff --git a/config/routes.rb b/config/routes.rb
index 52a026f19b0af84a4465dd060efe0c12fa33c196..b4da278357d1a54a7c84cb926278b9972187e9cd 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -182,9 +182,10 @@ Rails.application.routes.draw do
     delete ':id',                                  to: 'categories#destroy', as: :destroy_category
     get    ':id/feed',                             to: 'categories#rss_feed', as: :category_feed
     get    ':id/tags',                             to: 'tags#category', as: :category_tags
-    get    ':id/tags/:tag_id',                       to: 'tags#show', as: :tag
-    get    ':id/tags/:tag_id/edit',                  to: 'tags#edit', as: :edit_tag
-    patch  ':id/tags/:tag_id/edit',                  to: 'tags#update', as: :update_tag
+    get    ':id/tags/:tag_id',                     to: 'tags#show', as: :tag
+    get    ':id/tags/:tag_id/children',            to: 'tags#children', as: :tag_children
+    get    ':id/tags/:tag_id/edit',                to: 'tags#edit', as: :edit_tag
+    patch  ':id/tags/:tag_id/edit',                to: 'tags#update', as: :update_tag
   end
 
   get   'warning',                         to: 'mod_warning#current', as: :current_mod_warning
diff --git a/db/migrate/20200630105117_allow_tag_parents.rb b/db/migrate/20200630105117_allow_tag_parents.rb
new file mode 100644
index 0000000000000000000000000000000000000000..864a821fcdeb0b62acd2697f942bb1c0730ecc02
--- /dev/null
+++ b/db/migrate/20200630105117_allow_tag_parents.rb
@@ -0,0 +1,5 @@
+class AllowTagParents < ActiveRecord::Migration[5.2]
+  def change
+    add_reference :tags, :parent, foreign_key: { to_table: :tags }
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8193a72045fb86b874e62a1a2a7760bf6a6aacb9..3578990e13a60f58a59fc67ec9d9f289d7681a41 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2020_06_30_001048) do
+ActiveRecord::Schema.define(version: 2020_06_30_105117) do
 
   create_table "active_storage_attachments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", force: :cascade do |t|
     t.string "name", null: false
@@ -366,7 +366,9 @@ ActiveRecord::Schema.define(version: 2020_06_30_001048) do
     t.text "wiki_markdown"
     t.text "wiki"
     t.text "excerpt"
+    t.bigint "parent_id"
     t.index ["community_id"], name: "index_tags_on_community_id"
+    t.index ["parent_id"], name: "index_tags_on_parent_id"
     t.index ["tag_set_id"], name: "index_tags_on_tag_set_id"
   end
 
@@ -468,6 +470,7 @@ ActiveRecord::Schema.define(version: 2020_06_30_001048) do
   add_foreign_key "suggested_edits", "users"
   add_foreign_key "suggested_edits", "users", column: "decided_by_id"
   add_foreign_key "tags", "communities"
+  add_foreign_key "tags", "tags", column: "parent_id"
   add_foreign_key "votes", "communities"
   add_foreign_key "warning_templates", "communities"
   add_foreign_key "warnings", "community_users"
diff --git a/db/scripts/tag_children.sql b/db/scripts/tag_children.sql
new file mode 100644
index 0000000000000000000000000000000000000000..0fbc433ba5b51b2c3b74100f7bb35b96a28afb9a
--- /dev/null
+++ b/db/scripts/tag_children.sql
@@ -0,0 +1,10 @@
+WITH RECURSIVE CTE (id, group_id) AS (
+    SELECT id, parent_id
+    FROM tags
+    WHERE parent_id = $ParentId
+    UNION ALL
+    SELECT t.id, t.parent_id
+    FROM tags t
+    INNER JOIN CTE ON t.parent_id = CTE.id
+)
+SELECT * FROM CTE;
\ No newline at end of file
diff --git a/test/fixtures/tags.yml b/test/fixtures/tags.yml
index 8c9f95358088d31a604d2c1ff77e842670c5795f..d35e8f1c03e2499496f4668a33f426d6933f31c5 100644
--- a/test/fixtures/tags.yml
+++ b/test/fixtures/tags.yml
@@ -1,35 +1,29 @@
 discussion:
   name: discussion
-  description: discussion
   community: sample
   tag_set: main
 
 support:
   name: support
-  description: support
   community: sample
   tag_set: main
 
 bug:
   name: bug
-  description: bug
   community: sample
   tag_set: main
 
 feature-request:
   name: feature-request
-  description: feature-request
   community: sample
   tag_set: main
 
 faq:
   name: faq
-  description: faq
   community: sample
   tag_set: meta
 
 status-completed:
   name: status-completed
-  description: status-completed
   community: sample
   tag_set: meta