diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index a9ebd1b73d5c5e87edc90bbef9a18c4f4b44ab8d..39dbfe2e4710f9b56bdb7bc1d9254c25bbe92065 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -29,14 +29,23 @@ class TagsController < ApplicationController
             end
   end
 
-  def show; end
-
-  def edit
-
+  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)
+                 .includes(:post_type, :tags).list_includes.paginate(page: params[:page], per_page: 50)
+                 .order(sort_param)
   end
 
-  def update
+  def edit; end
 
+  def update
+    if @tag.update(tag_params.merge(wiki: helpers.render_markdown(params[:tag][:wiki_markdown])))
+      redirect_to tag_path(id: @category.id, tag_id: @tag.id)
+    else
+      render :edit
+    end
   end
 
   private
@@ -55,4 +64,8 @@ class TagsController < ApplicationController
   def set_category
     @category = Category.find params[:id]
   end
+
+  def tag_params
+    params.require(:tag).permit(:excerpt, :wiki_markdown)
+  end
 end
diff --git a/app/models/tag.rb b/app/models/tag.rb
index 1fc46a351aa920c318c1ae2d72b6d444b67f671c..efe46842d65bc9e1825082bc8e2018f3f4f574fc 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -4,6 +4,9 @@ class Tag < ApplicationRecord
   has_and_belongs_to_many :posts
   belongs_to :tag_set
 
+  validates :excerpt, length: { maximum: 600 }, allow_blank: true
+  validates :wiki_markdown, length: { maximum: 30000 }, allow_blank: true
+
   def self.search(term)
     where('name LIKE ?', "%#{sanitize_sql_like(term)}%")
       .order(sanitize_sql_array(['name LIKE ? DESC, name', "#{sanitize_sql_like(term)}%"]))
diff --git a/app/views/shared/_body_field.html.erb b/app/views/shared/_body_field.html.erb
index df39db82f7c03ee39381a9b61e470ff70c62375b..206d9d30df499c4e71417b1f160f020de40bdd60 100644
--- a/app/views/shared/_body_field.html.erb
+++ b/app/views/shared/_body_field.html.erb
@@ -1,5 +1,8 @@
 <div class="form-group">
   <%= f.label field_name, field_label, class: "form-element" %>
+  <% if block_given? %>
+    <span class="form-caption"><%= yield %></span>
+  <% end %>
   <div class="widget">
     <% classes = 'form-element post-field js-post-field widget--body h-b-0 h-m-0' %>
     <% key = "saved_post.#{current_user&.id}.#{request.path}" %>
diff --git a/app/views/tags/_tag.html.erb b/app/views/tags/_tag.html.erb
index 27691270f81f202bb1295ff3ec4a015d517898cb..a5a3799572704f73b2f6406a0d67fe913b5d3ba9 100644
--- a/app/views/tags/_tag.html.erb
+++ b/app/views/tags/_tag.html.erb
@@ -2,8 +2,12 @@
   <%= link_to tag.name, tag_path(id: category.id, tag_id: tag.id), class: classes %>
   <span class="has-color-tertiary-900">&times; <%= tag.post_count %></span>
   <% if tag.excerpt.present? %>
-    <p class="has-font-size-caption">
-      <%= split_words_max_length(strip_markdown(tag.excerpt_markdown), 150) %>
+    <p class="has-font-size-caption has-color-tertiary-900">
+      <% splat = split_words_max_length(tag.excerpt, 120) %>
+      <%= splat[0] %>
+      <% if splat.size > 1 %>
+        ...
+      <% end %>
     </p>
   <% end %>
 </div>
\ No newline at end of file
diff --git a/app/views/tags/edit.html.erb b/app/views/tags/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..487d673dbf76a22495b9a82ea0c5e9e226ed7dfa
--- /dev/null
+++ b/app/views/tags/edit.html.erb
@@ -0,0 +1,39 @@
+<% content_for :title, 'Edit tag' %>
+
+<%= render 'posts/markdown_script' %>
+
+<h1>
+  Edit <span class="<%= @classes %> is-large"><%= @tag.name %></span>
+</h1>
+
+<% if @tag.errors.any? %>
+  <div class="notice is-danger">
+    There were some errors while saving this tag:
+
+    <ul>
+      <% @tag.errors.full_messages.each do |msg| %>
+        <li><%= msg %></li>
+      <% end %>
+    </ul>
+  </div>
+<% end %>
+
+<%= form_for @tag, url: update_tag_path(id: @category.id, tag_id: @tag.id) do |f| %>
+  <div class="form-group">
+    <%= f.label :excerpt, 'Usage guidance', class: 'form-element' %>
+    <span class="form-caption">
+      Short usage guidance for this tag. Will be cut off at 120 characters in the tags list, but displayed in full on
+      the tag page.
+    </span>
+    <%= f.text_area :excerpt, class: 'form-element js-tag-excerpt', rows: 3 %>
+    <span class="has-float-right has-font-size-caption js-character-count"
+          data-target=".js-tag-excerpt" data-max="600">0 / 600</span>
+  </div>
+
+  <%= render 'shared/body_field', f: f, field_name: :wiki_markdown, field_label: 'Wiki', post: @tag do %>
+    Full usage guidance and any other information you want people to know about this tag.
+  <% end %>
+  <div class="post-preview"></div>
+
+  <%= f.submit 'Save', class: 'button is-filled' %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/tags/show.html.erb b/app/views/tags/show.html.erb
index 0deb485160059106b922b1186556d49283d73d09..9f815fda137ee3f61cdc4933c86ebf7ca34b000d 100644
--- a/app/views/tags/show.html.erb
+++ b/app/views/tags/show.html.erb
@@ -1,12 +1,14 @@
+<% content_for :title, "Posts tagged #{@tag.name}" %>
+
 <h1>
   Posts tagged <span class="<%= @classes %> is-large"><%= @tag.name %></span>
 </h1>
 
 <div class="widget">
-  <div class="widget--body">
-    <%= raw(sanitize(@tag.excerpt, scrubber: scrubber)) %>
+  <div class="widget--body has-font-size-caption has-color-tertiary-900">
+    <%= raw(sanitize(@tag.excerpt, scrubber: scrubber).gsub("\n", '<br/>')) %>
     <% unless @tag.excerpt.present? %>
-      <p class="has-font-size-caption">
+      <p class="has-font-size-caption has-margin-0">
         <em>
           This tag doesn't have any usage information yet.
           <% if current_user&.has_privilege?('EditTag') %>
@@ -15,10 +17,11 @@
         </em>
       </p>
     <% end %>
-    <hr/>
+  </div>
+  <div class="widget--body">
     <%= raw(sanitize(@tag.wiki, scrubber: scrubber)) %>
     <% unless @tag.wiki.present? %>
-      <p class="has-font-size-caption">
+      <p class="has-font-size-caption has-margin-0">
         <em>
           This tag doesn't have a detailed wiki yet.
           <% if current_user&.has_privilege?('EditTag') %>
@@ -29,3 +32,39 @@
     <% end %>
   </div>
 </div>
+
+<% if current_user&.has_privilege?('EditTag') %>
+  <%= link_to 'Edit', edit_tag_path(id: @category.id, tag_id: @tag.id), class: 'button is-muted is-outlined' %>
+<% end %>
+
+<% post_count = @posts.count %>
+<div class="has-color-tertiary-500 category-meta" title="<%= post_count %>">
+  <div>
+    <%= short_number_to_human post_count, precision: 1, significant: false %>
+    <%= 'post'.pluralize(post_count) %>
+  </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 %>
+  </div>
+</div>
+
+<div class="item-list">
+  <% @posts.each do |post| %>
+    <%= render 'posts/type_agnostic', post: post %>
+  <% end %>
+</div>
+
+<div class="has-padding-top-4">
+  <%= will_paginate @posts, renderer: BootstrapPagination::Rails %>
+</div>
diff --git a/db/migrate/20200630001048_remove_excerpt_markdown.rb b/db/migrate/20200630001048_remove_excerpt_markdown.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cb9ea7ce76723235a319b940733d32f215156ae8
--- /dev/null
+++ b/db/migrate/20200630001048_remove_excerpt_markdown.rb
@@ -0,0 +1,5 @@
+class RemoveExcerptMarkdown < ActiveRecord::Migration[5.2]
+  def change
+    remove_column :tags, :excerpt_markdown
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index fe14f492fbe26fcedfbd577d0bc54ed2fbca5052..8193a72045fb86b874e62a1a2a7760bf6a6aacb9 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_29_131408) do
+ActiveRecord::Schema.define(version: 2020_06_30_001048) 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
@@ -364,7 +364,6 @@ ActiveRecord::Schema.define(version: 2020_06_29_131408) do
     t.bigint "community_id", null: false
     t.bigint "tag_set_id", null: false
     t.text "wiki_markdown"
-    t.text "excerpt_markdown"
     t.text "wiki"
     t.text "excerpt"
     t.index ["community_id"], name: "index_tags_on_community_id"