diff --git a/claro.info.yml b/claro.info.yml
index ee390e6..7109a9e 100644
--- a/claro.info.yml
+++ b/claro.info.yml
@@ -51,9 +51,11 @@ libraries-override:
   classy/base:
     css:
       component:
+        css/components/breadcrumb.css: false
         css/components/details.css: false
         css/components/form.css: false
-        css/components/breadcrumb.css: false
+        css/components/tableselect.css: css/dist/components/tableselect.css
+        css/components/tabledrag.css: css/dist/components/tabledrag.css
   # @todo Refactor when https://www.drupal.org/node/2642122 is fixed.
   filter/drupal.filter.admin:
     css:
diff --git a/claro.theme b/claro.theme
index 26f3662..3d59423 100644
--- a/claro.theme
+++ b/claro.theme
@@ -6,7 +6,9 @@
  */
 
 use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\GeneratedLink;
 use Drupal\Core\Link;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Url;
@@ -431,3 +433,64 @@ function claro_preprocess_radios(&$variables) {
 function claro_preprocess_filter_tips(&$variables) {
   $variables['#attached']['library'][] = 'filter/drupal.filter';
 }
+
+/**
+ * Implements template_preprocess_HOOK() for table.
+ *
+ * @todo Revisit after https://www.drupal.org/node/3025726 or
+ * https://www.drupal.org/node/1973418 is in.
+ */
+function claro_preprocess_table(&$variables) {
+  if (!empty($variables['header'])) {
+    foreach ($variables['header'] as &$header_cell) {
+      // For 8.6.x and below.
+      // @todo Remove this after 8.6.x is out of support.
+      if ($header_cell['content'] instanceof GeneratedLink) {
+        $dom_doc = Html::load($header_cell['content']->getGeneratedLink());
+        $anchors = $dom_doc->getElementsByTagName('a');
+
+        if (!empty($anchors)) {
+          foreach ($anchors as $anchor) {
+            $anchor_href = $anchor->getAttribute('href');
+            $parsed_url = UrlHelper::parse($anchor_href);
+            $query = !empty($parsed_url['query']) ? $parsed_url['query'] : [];
+
+            if (isset($query['order']) && isset($query['sort'])) {
+              $header_cell['attributes']->addClass('sortable-heading');
+            }
+          }
+        }
+      }
+
+      // For 8.7.x and above.
+      if ($header_cell['content'] instanceof Link) {
+        $query = $header_cell['content']->getUrl()->getOption('query') ?: [];
+
+        if (isset($query['order']) && isset($query['sort'])) {
+          $header_cell['attributes']->addClass('sortable-heading');
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implements template_preprocess_HOOK() for views_view_table.
+ *
+ * @todo Revisit after https://www.drupal.org/node/3025726 or
+ * https://www.drupal.org/node/1973418 is in.
+ */
+function claro_preprocess_views_view_table(&$variables) {
+  if (!empty($variables['header'])) {
+    foreach ($variables['header'] as &$header_cell) {
+      if (!empty($header_cell['url'])) {
+        $parsed_url = UrlHelper::parse($header_cell['url']);
+        $query = !empty($parsed_url['query']) ? $parsed_url['query'] : [];
+
+        if (isset($query['order']) && isset($query['sort'])) {
+          $header_cell['attributes']->addClass('sortable-heading');
+        }
+      }
+    }
+  }
+}
diff --git a/css/src/base/variables.css b/css/src/base/variables.css
index 0143fec..4810fa7 100644
--- a/css/src/base/variables.css
+++ b/css/src/base/variables.css
@@ -17,6 +17,11 @@
   /* Variations. */
   --color-absolutezero-hover: #003ebb;
   --color-absolutezero-active: #00339a;
+  --color-focus: #b3c9f5;
+  --color-bgblue-hover: #f0f5fd;
+  --color-bgblue-active: #e6ecf8;
+  --color-bgred-hover: #fdf5f5;
+  --color-bgred-active: #fceded;
   /*
    * Base.
    */
diff --git a/css/src/components/ajax-progress.module.css b/css/src/components/ajax-progress.module.css
index fadf1d8..b622d01 100644
--- a/css/src/components/ajax-progress.module.css
+++ b/css/src/components/ajax-progress.module.css
@@ -92,6 +92,7 @@
  * IE11 and Edge are not handling these rem values smart enought.
  */
 /* This is a fix for ie11... */
+/* stylelint-disable-next-line selector-type-no-unknown */
 _:-ms-fullscreen,
 .ajax-progress-fullscreen::before {
   width: 30px;
@@ -100,6 +101,7 @@ _:-ms-fullscreen,
   background-size: 30px 30px;
 }
 /* ..and this is a fix for edge. */
+/* stylelint-disable-next-line selector-type-no-unknown */
 _:-ms-lang(x),
 .ajax-progress-fullscreen::before {
   width: 30px;
diff --git a/css/src/components/breadcrumb.css b/css/src/components/breadcrumb.css
index f94eb28..0fc1b51 100644
--- a/css/src/components/breadcrumb.css
+++ b/css/src/components/breadcrumb.css
@@ -25,7 +25,7 @@
 
 .breadcrumb__item + .breadcrumb__item::before {
   content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 5 8' height='8' width='5'%3E%3Cpath d='M1.2070312,0.64696878 0.5,1.354 3.1464844,4.0004844 0.5,6.6469688 1.2070312,7.354 4.5605468,4.0004844Z' fill='%23545560'/%3E%3C/svg%3E");
-  padding: 0 .75rem;
+  padding: 0 0.75rem;
 }
 
 [dir="rtl"] .breadcrumb__item + .breadcrumb__item::before {
diff --git a/css/src/components/tabledrag.css b/css/src/components/tabledrag.css
new file mode 100644
index 0000000..8f6d92f
--- /dev/null
+++ b/css/src/components/tabledrag.css
@@ -0,0 +1,12 @@
+/**
+ * @file
+ * Visual styles for table drag.
+ */
+
+.draggable.drag,
+.draggable.drag:focus {
+  background-color: #fffff0;
+}
+.draggable.drag-previous {
+  background-color: #ffd;
+}
diff --git a/css/src/components/tables.css b/css/src/components/tables.css
index e616e93..4f33d01 100644
--- a/css/src/components/tables.css
+++ b/css/src/components/tables.css
@@ -4,9 +4,14 @@
  */
 
 table {
-  width: 100%;
-  margin: 0 0 10px;
+  min-width: 100%;
+  margin-top: 1.5rem;
+  margin-bottom: 1.5rem;
 }
+.sticky-header {
+  min-width: 0;
+}
+
 caption {
   text-align: left; /* LTR */
 }
@@ -14,101 +19,137 @@ caption {
   text-align: right;
 }
 th {
+  box-sizing: border-box;
+  height: 3rem;
+  padding: 0.5rem 1rem;
+  background: var(--color-whitesmoke);
+  line-height: 1.25rem;
   text-align: left; /* LTR */
-  padding: 10px 12px;
+  position: relative;
 }
 [dir="rtl"] th {
   text-align: right;
 }
-thead th {
-  background: #f5f5f2;
-  border: solid #bfbfba;
-  border-width: 1px 0;
-  color: #333;
-  text-transform: uppercase;
+
+/**
+ * Table sort.
+ */
+/* Table head cell containing sort link. */
+.sortable-heading {
+  padding: 0 1rem;
 }
-tr {
-  border-bottom: 1px solid #e6e4df;
-  padding: 0.1em 0.6em;
+/* The actual sort link. */
+.sortable-heading > a {
+  display: block;
+  padding: 0.5rem 1.5rem 0.5rem 0; /* LTR */
+  color: inherit;
 }
-thead > tr {
-  border-bottom: 1px solid #000;
+[dir="rtl"] .sortable-heading > a {
+  padding-right: 0;
+  padding-left: 1.5rem;
 }
-tbody tr:hover,
-tbody tr:focus {
-  background: #f7fcff;
+.sortable-heading > a::before {
+  position: absolute;
+  top: 0;
+  right: 1rem;
+  bottom: 0;
+  left: 1rem;
+  z-index: 0;
+  display: block;
+  border-bottom: 0.125rem solid transparent;
+  content: "";
 }
-/* See colors.css */
-tbody tr.color-warning:hover,
-tbody tr.color-warning:focus {
-  background: #fdf8ed;
+/* stylelint-disable-next-line selector-type-no-unknown */
+_:-ms-fullscreen, /* Only IE 11 */
+.sortable-heading > a::before {
+  top: auto;
+  height: 100%;
 }
-tbody tr.color-error:hover,
-tbody tr.color-error:focus {
-  background: #fcf4f2;
+.sortable-heading > a::after {
+  position: absolute;
+  top: 50%;
+  right: 1rem;
+  width: 0.875rem;
+  height: 1rem;
+  margin-top: -0.5rem;
+  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='8' viewBox='0 0 14 8'%3E%3Cpath d='m1.75,0.25v1.5h10.5v-1.5z m0,3v1.5h7.5v-1.5z m0,3v1.5h4.5v-1.5z' fill='%23222330'/%3E%3C/svg%3E") no-repeat 50% 50%;
+  background-size: contain;
+  opacity: 0.5;
+  content: "";
+}
+/* stylelint-disable-next-line selector-type-no-unknown */
+_:-ms-fullscreen, /* Only IE 11 */
+.sortable-heading > a::after {
+  position: static;
+  float: right;
+  margin-top: 0.125rem; /* 2px */
+  margin-right: -1.5rem; /* -24px */
+}
+[dir="rtl"] .sortable-heading > a::after {
+  right: auto;
+  left: 1rem;
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='8' viewBox='0 0 14 8'%3E%3Cpath d='m12.25,0.25v1.5H1.75v-1.5z m0,3v1.5h-7.5v-1.5z m0,3v1.5h-4.5v-1.5z' fill='%23222330'/%3E%3C/svg%3E");
+}
+/* stylelint-disable-next-line selector-type-no-unknown */
+_:-ms-fullscreen, /* Only IE 11 */
+[dir="rtl"] .sortable-heading > a::after {
+  float: left;
+  margin-right: 0;
+  margin-left: -1.5rem; /* -24px */
+}
+/* Sortable cell's link focus/hover state. */
+.sortable-heading > a:focus,
+.sortable-heading > a:hover {
+  text-decoration: none;
 }
-
-table.no-highlight tr.selected td {
-  background: transparent;
+.sortable-heading > a:focus::before,
+.sortable-heading > a:hover::before {
+  border-color: inherit;
 }
-
-td,
-th {
-  vertical-align: middle;
+.sortable-heading > a:focus::after,
+.sortable-heading > a:hover::after {
+  opacity: 1;
 }
-td {
-  padding: 10px 12px;
-  text-align: left; /* LTR */
+/* Sortable cell's active state. */
+.sortable-heading.is-active > a {
+  color: var(--color-absolutezero);
 }
-[dir="rtl"] td {
-  text-align: right;
+.sortable-heading.is-active > a::before {
+  border-bottom: 0.1875rem solid var(--color-absolutezero);
 }
-th > a {
-  position: relative;
-  display: block;
+.sortable-heading.is-active > a::after {
+  content: none;
 }
 
-/* 1. Must match negative bottom padding of the parent <th> */
-th > a:after {
-  content: '';
-  display: block;
-  position: absolute;
-  top: 0;
-  bottom: -10px; /* 1. */
-  left: 0;
-  right: 0;
-  border-bottom: 2px solid transparent;
-  -webkit-transition: all 0.1s;
-  transition: all 0.1s;
+tr {
+  border-bottom: 0.0625rem solid var(--color-lightgray);
 }
-th.is-active > a {
-  color: #004875;
+thead tr {
+  border: 0;
 }
-th.is-active img {
-  position: absolute;
-  right: 0; /* LTR */
-  top: 50%;
+tr:hover,
+tr:focus {
+  background: var(--color-bgblue-hover);
 }
-[dir="rtl"] th.is-active img {
-  right: auto;
-  left: 0;
+tr.color-warning:hover,
+tr.color-warning:focus {
+  background: #fdf8ed;
 }
-th.is-active > a:after {
-  border-bottom-color: #004875;
+tr.color-error:hover,
+tr.color-error:focus {
+  background: #fcf4f2;
 }
-th > a:hover,
-th > a:focus,
-th.is-active > a:hover,
-th.is-active > a:focus {
-  color: #008ee6;
-  text-decoration: none;
+
+td {
+  padding: 0.5rem 1rem;
+  text-align: left; /* LTR */
+  box-sizing: border-box;
+  min-height: 4rem;
 }
-th > a:hover:after,
-th > a:focus:after,
-th.is-active > a:hover:after,
-th.is-active > a:focus:after {
-  border-bottom-color: #008ee6;
+[dir="rtl"] td {
+  text-align: right;
 }
+
 td .item-list ul {
   margin: 0;
 }
@@ -129,24 +170,39 @@ th.select-all {
  * Captions.
  */
 .caption {
-  margin-bottom: 1.2em;
+  margin-bottom: 1.25rem; /* 20px */
+}
+
+tfoot {
+  font-weight: bold;
+}
+tfoot tr:last-child {
+  border-bottom: 0;
+}
+tfoot tr:first-child td {
+  border-top: 0.0625rem solid var(--color-grayblue);
 }
 
 /**
- * Responsive tables.
+ * Responsive table cells.
  */
-@media screen and (max-width: 37.5em) { /* 600px */
-  th.priority-low,
-  td.priority-low,
+th.priority-low,
+th.priority-medium,
+td.priority-low,
+td.priority-medium {
+  display: none;
+}
+
+@media screen and (min-width: 38em) {
   th.priority-medium,
   td.priority-medium {
-    display: none;
+    display: table-cell;
   }
 }
 
-@media screen and (max-width: 60em) { /* 920px */
+@media screen and (min-width: 60em) {
   th.priority-low,
   td.priority-low {
-    display: none;
+    display: table-cell;
   }
 }
diff --git a/css/src/components/tableselect.css b/css/src/components/tableselect.css
new file mode 100644
index 0000000..c539b09
--- /dev/null
+++ b/css/src/components/tableselect.css
@@ -0,0 +1,20 @@
+/**
+ * @file
+ * Table select — replaces implementation of Classy theme.
+ *
+ * @see tableselect.js
+ */
+
+td.checkbox,
+th.checkbox {
+  text-align: center;
+}
+[dir="rtl"] td.checkbox,
+[dir="rtl"] th.checkbox {
+  /* This is required to win over specificity of [dir="rtl"] td */
+  text-align: center;
+}
+
+.selected td {
+  background-color: var(--color-bgblue-active);
+}
diff --git a/css/src/components/tablesort-indicator.css b/css/src/components/tablesort-indicator.css
index fa49af7..6051e74 100644
--- a/css/src/components/tablesort-indicator.css
+++ b/css/src/components/tablesort-indicator.css
@@ -4,23 +4,43 @@
  */
 
 .tablesort {
-  float: right; /* LTR */
-  margin-top: 5px;
-  width: 10px;
-  height: 10px;
+  margin-top: -0.5rem; /* -8px */
+  position: absolute;
+  top: 50%;
+  right: 1rem;
+  width: 0.875rem; /* 14px */
+  height: 1rem; /* 16px */
+  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='8' viewBox='0 0 14 8'%3E%3Cpath d='m1.75,0.25v1.5h10.5v-1.5z m0,3v1.5h7.5v-1.5z m0,3v1.5h4.5v-1.5z' fill='%23222330'/%3E%3C/svg%3E") no-repeat 0 50%;
+  background-size: auto;
+  opacity: 0.5;
 }
-[dir="rtl"] .tablesort {
-  float: left;
+/* stylelint-disable-next-line selector-type-no-unknown */
+_:-ms-fullscreen, /* Only IE 11 */
+.tablesort {
+  position: static;
+  float: right;
+  margin-top: 0.125rem; /* 2px */
+  margin-right: -1.5rem; /* -24px */
 }
-.tablesort--asc {
-  background-image: url(../../../../misc/icons/004875/twistie-down.svg);
+[dir="rtl"] .tablesort {
+  right: auto;
+  left: 1rem; /* 16px */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='8' viewBox='0 0 14 8'%3E%3Cpath d='m12.25,0.25v1.5H1.75v-1.5z m0,3v1.5h-7.5v-1.5z m0,3v1.5h-4.5v-1.5z' fill='%23222330'/%3E%3C/svg%3E");
 }
-a:hover .tablesort--asc {
-  background-image: url(../../../../misc/icons/008ee6/twistie-down.svg);
+/* stylelint-disable-next-line selector-type-no-unknown */
+_:-ms-fullscreen, /* Only IE 11 */
+[dir="rtl"] .tablesort {
+  margin-left: -1.5rem; /* -24px */
+  margin-right: 0;
+  float: left;
 }
-.tablesort--desc {
-  background-image: url(../../../../misc/icons/004875/twistie-up.svg);
+.tablesort--asc,
+[dir="rtl"] .tablesort--asc {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='16' viewBox='0 0 10 12'%3E%3Cpath d='M 5 0.43945312 L 0.71875 4.71875 L 1.78125 5.78125 L 4.25 3.3125 L 4.25 11.25 L 5.75 11.25 L 5.75 3.3125 L 8.21875 5.78125 L 9.28125 4.71875 L 5 0.43945312 z' fill='%23004adc'/%3E%3C/svg%3E");
+  opacity: 1;
 }
-a:hover .tablesort--desc {
-  background-image: url(../../../../misc/icons/008ee6/twistie-up.svg);
+.tablesort--desc,
+[dir="rtl"] .tablesort--desc {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='16' viewBox='0 0 10 12'%3E%3Cpath d='M 4.25 0.75 L 4.25 8.6875 L 1.78125 6.21875 L 0.71875 7.28125 L 5 11.560547 L 9.28125 7.28125 L 8.21875 6.21875 L 5.75 8.6875 L 5.75 0.75 L 4.25 0.75 z' fill='%23004adc'/%3E%3C/svg%3E");
+  opacity: 1;
 }
diff --git a/images/src/sort--asc.svg b/images/src/sort--asc.svg
new file mode 100644
index 0000000..3e0a627
--- /dev/null
+++ b/images/src/sort--asc.svg
@@ -0,0 +1,9 @@
+<svg
+  xmlns='http://www.w3.org/2000/svg'
+  width='14'
+  height='12'
+  viewBox='0 0 10 12'>
+  <path
+    d='M 5 0.43945312 L 0.71875 4.71875 L 1.78125 5.78125 L 4.25 3.3125 L 4.25 11.25 L 5.75 11.25 L 5.75 3.3125 L 8.21875 5.78125 L 9.28125 4.71875 L 5 0.43945312 z'
+    fill='#004adc'/>
+</svg>
diff --git a/images/src/sort--desc.svg b/images/src/sort--desc.svg
new file mode 100644
index 0000000..d84a5d5
--- /dev/null
+++ b/images/src/sort--desc.svg
@@ -0,0 +1,9 @@
+<svg
+  xmlns='http://www.w3.org/2000/svg'
+  width='14'
+  height='12'
+  viewBox='0 0 10 12'>
+  <path
+    d='M 4.25 0.75 L 4.25 8.6875 L 1.78125 6.21875 L 0.71875 7.28125 L 5 11.560547 L 9.28125 7.28125 L 8.21875 6.21875 L 5.75 8.6875 L 5.75 0.75 L 4.25 0.75 z'
+    fill='#004adc'/>
+</svg>
diff --git a/images/src/sort--inactive--ltr.svg b/images/src/sort--inactive--ltr.svg
new file mode 100644
index 0000000..f34a82b
--- /dev/null
+++ b/images/src/sort--inactive--ltr.svg
@@ -0,0 +1,9 @@
+<svg
+  xmlns='http://www.w3.org/2000/svg'
+  width='14'
+  height='8'
+  viewBox='0 0 14 8'>
+  <path
+    d='m 1.75,0.25 v 1.5 h 10.5 v -1.5 z m 0,3 v 1.5 h 7.5 v -1.5 z m 0,3 v 1.5 h 4.5 v -1.5 z'
+    fill='#8e929c'/>
+</svg>
diff --git a/images/src/sort--inactive--rtl.svg b/images/src/sort--inactive--rtl.svg
new file mode 100644
index 0000000..b50377f
--- /dev/null
+++ b/images/src/sort--inactive--rtl.svg
@@ -0,0 +1,9 @@
+<svg
+  xmlns='http://www.w3.org/2000/svg'
+  width='14'
+  height='8'
+  viewBox='0 0 14 8'>
+  <path
+    d='m 12.25,0.25 v 1.5 H 1.75 v -1.5 z m 0,3 v 1.5 h -7.5 v -1.5 z m 0,3 v 1.5 h -4.5 v -1.5 z'
+    fill='#8e929c'/>
+</svg>
diff --git a/templates/admin/tablesort-indicator.html.twig b/templates/admin/tablesort-indicator.html.twig
new file mode 100644
index 0000000..b681af3
--- /dev/null
+++ b/templates/admin/tablesort-indicator.html.twig
@@ -0,0 +1,28 @@
+{#
+/**
+ * @file
+ * Theme override for displaying a tablesort indicator.
+ *
+ * Available variables:
+ * - style: Either 'asc' or 'desc', indicating the sorting direction.
+ *
+ * @todo Remove after https://www.drupal.org/node/1973418 is in.
+ */
+#}
+{%
+  set classes = [
+    'tablesort',
+    'tablesort--' ~ style,
+  ]
+%}
+<span{{ attributes.addClass(classes) }}>
+  {% if style in ['asc', 'desc'] %}
+    <span class="visually-hidden">
+      {% if style == 'asc' -%}
+        {{ 'Sort ascending'|t }}
+      {% else -%}
+        {{ 'Sort descending'|t }}
+      {% endif %}
+    </span>
+  {% endif %}
+</span>
