diff --git a/e2e/test/scenarios/admin/datamodel/editor.cy.spec.js b/e2e/test/scenarios/admin/datamodel/editor.cy.spec.js
index 64984db66a35de6aea8db4ce5bce1c4d8b6d0ea9..82216574b2076e412a7c8545ccd1e2983fcb0e26 100644
--- a/e2e/test/scenarios/admin/datamodel/editor.cy.spec.js
+++ b/e2e/test/scenarios/admin/datamodel/editor.cy.spec.js
@@ -59,7 +59,6 @@ describe("scenarios > admin > datamodel > editor", () => {
       });
     });
 
-    // QUESTION - can we check update in the admin instead?
     it("should allow changing the table description", () => {
       visitTableMetadata();
       setValueAndBlurInput(ORDERS_DESCRIPTION, "New description");
@@ -67,6 +66,12 @@ describe("scenarios > admin > datamodel > editor", () => {
       cy.findByDisplayValue("New description").should("be.visible");
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
       cy.findByText("Updated Table description").should("be.visible");
+
+      cy.visit(`/reference/databases/${SAMPLE_DB_ID}/tables/${ORDERS_ID}`);
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Orders").should("be.visible");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("New description").should("be.visible");
     });
 
     it("should allow clearing the table description", () => {
@@ -75,6 +80,12 @@ describe("scenarios > admin > datamodel > editor", () => {
       cy.wait("@updateTable");
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
       cy.findByText("Updated Table description").should("be.visible");
+
+      cy.visit(`/reference/databases/${SAMPLE_DB_ID}/tables/${ORDERS_ID}`);
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Orders").should("be.visible");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("No description yet").should("be.visible");
     });
 
     it("should allow changing the table visibility", () => {
@@ -137,6 +148,14 @@ describe("scenarios > admin > datamodel > editor", () => {
         .should("be.visible");
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
       cy.findByText("Updated Total").should("be.visible");
+
+      cy.visit(
+        `/reference/databases/${SAMPLE_DB_ID}/tables/${ORDERS_ID}/fields/${ORDERS.TOTAL}`,
+      );
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Total").should("be.visible");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("New description").should("be.visible");
     });
 
     it("should allow clearing the field description", () => {
@@ -147,6 +166,14 @@ describe("scenarios > admin > datamodel > editor", () => {
       cy.wait("@updateField");
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
       cy.findByText("Updated Total").should("be.visible");
+
+      cy.visit(
+        `/reference/databases/${SAMPLE_DB_ID}/tables/${ORDERS_ID}/fields/${ORDERS.TOTAL}`,
+      );
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Total").should("be.visible");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("No description yet").should("be.visible");
     });
 
     it("should allow changing the field visibility", () => {
@@ -319,6 +346,14 @@ describe("scenarios > admin > datamodel > editor", () => {
       cy.findByDisplayValue("New description").should("be.visible");
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
       cy.findByText("Updated Total").should("be.visible");
+
+      cy.visit(
+        `/reference/databases/${SAMPLE_DB_ID}/tables/${ORDERS_ID}/fields/${ORDERS.TOTAL}`,
+      );
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Total").should("be.visible");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("New description").should("be.visible");
     });
 
     it("should allow changing the field visibility", () => {
diff --git a/e2e/test/scenarios/admin/datamodel/metrics.cy.spec.js b/e2e/test/scenarios/admin/datamodel/metrics.cy.spec.js
index 53f68be1c0d110c7d1eab54540e06bc644e4cb28..b8a387a11f8f7f1216a916208b70af4dfa3aa9c4 100644
--- a/e2e/test/scenarios/admin/datamodel/metrics.cy.spec.js
+++ b/e2e/test/scenarios/admin/datamodel/metrics.cy.spec.js
@@ -5,6 +5,8 @@ import {
   openOrdersTable,
   visualize,
   summarize,
+  filter,
+  filterField,
 } from "e2e/support/helpers";
 import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
 import { createMetric } from "e2e/support/helpers/e2e-table-metadata-helpers";
@@ -82,6 +84,16 @@ describe("scenarios > admin > datamodel > metrics", () => {
       );
     });
 
+    it("should show how to create metrics", () => {
+      cy.visit("/reference/metrics");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText(
+        "Metrics are the official numbers that your team cares about",
+      );
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Learn how to create metrics");
+    });
+
     it("custom expression aggregation should work in metrics (metabase#22700)", () => {
       cy.intercept("POST", "/api/dataset").as("dataset");
 
@@ -136,6 +148,48 @@ describe("scenarios > admin > datamodel > metrics", () => {
       });
     });
 
+    it("should show no questions based on a new metric", () => {
+      cy.visit("/reference/metrics/1/questions");
+      cy.findAllByText("Questions about orders < 100");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Loading...");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Loading...").should("not.exist");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText(
+        "Questions about this metric will appear here as they're added",
+      );
+    });
+
+    it("should see a newly asked question in its questions list", () => {
+      // Ask a new qustion
+      cy.visit("/reference/metrics/1/questions");
+      cy.get(".full").find(".Button").click();
+
+      filter();
+      filterField("Total", {
+        placeholder: "min",
+        value: "50",
+      });
+
+      cy.findByTestId("apply-filters").click();
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Save").click();
+      cy.findAllByText("Save").last().click();
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Not now").click();
+
+      // Check the list
+      cy.visit("/reference/metrics/1/questions");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Our analysis").should("not.exist");
+      cy.findAllByText("Questions about orders < 100");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText(
+        "Orders, orders < 100, Filtered by Total is greater than or equal to 50",
+      );
+    });
+
     it("should show the metric detail view for a specific id", () => {
       cy.visit("/admin/datamodel/metric/1");
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
diff --git a/e2e/test/scenarios/admin/datamodel/segments.cy.spec.js b/e2e/test/scenarios/admin/datamodel/segments.cy.spec.js
index 21bee16c1ea71cb207ab1413e9067450c3801ff8..b74cff964cc358ef48f1fc76a4411e6e561afcad 100644
--- a/e2e/test/scenarios/admin/datamodel/segments.cy.spec.js
+++ b/e2e/test/scenarios/admin/datamodel/segments.cy.spec.js
@@ -1,5 +1,11 @@
 // Ported from `segments.e2e.spec.js`
-import { restore, popover, modal } from "e2e/support/helpers";
+import {
+  restore,
+  popover,
+  modal,
+  filter,
+  filterField,
+} from "e2e/support/helpers";
 
 import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
 import { createSegment } from "e2e/support/helpers/e2e-table-metadata-helpers";
@@ -50,6 +56,14 @@ describe("scenarios > admin > datamodel > segments", () => {
         cy.findByText("Custom Expression");
       });
     });
+
+    it("should show no segments", () => {
+      cy.visit("/reference/segments");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Segments are interesting subsets of tables");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Learn how to create segments");
+    });
   });
 
   describe("with segment", () => {
@@ -69,6 +83,29 @@ describe("scenarios > admin > datamodel > segments", () => {
       });
     });
 
+    it("should show the segment fields list and detail view", () => {
+      // In the list
+      cy.visit("/reference/segments");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText(SEGMENT_NAME);
+
+      // Detail view
+      cy.visit("/reference/segments/1");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Description");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("See this segment");
+
+      // Segment fields
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Fields in this segment").click();
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("See this segment").should("not.exist");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText(`Fields in ${SEGMENT_NAME}`);
+      cy.findAllByText("Discount");
+    });
+
     it("should show up in UI list", () => {
       cy.visit("/admin/datamodel/segments");
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
@@ -85,6 +122,46 @@ describe("scenarios > admin > datamodel > segments", () => {
       cy.findByText("Preview");
     });
 
+    it("should show no questions based on a new segment", () => {
+      cy.visit("/reference/segments/1/questions");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText(`Questions about ${SEGMENT_NAME}`);
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText(
+        "Questions about this segment will appear here as they're added",
+      );
+    });
+
+    it("should see a newly asked question in its questions list", () => {
+      // Ask question
+      cy.visit("/reference/segments/1/questions");
+      cy.get(".full .Button").click();
+      cy.findAllByText("37.65");
+
+      filter();
+      filterField("Product ID", {
+        value: "14",
+      });
+      cy.findByTestId("apply-filters").click();
+
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Product ID is 14");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText("Save").click();
+      cy.findAllByText("Save").last().click();
+
+      // Check list
+      cy.visit("/reference/segments/1/questions");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText(
+        "Questions about this segment will appear here as they're added",
+      ).should("not.exist");
+      // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+      cy.findByText(
+        `Orders, Filtered by ${SEGMENT_NAME} and Product ID equals 14`,
+      );
+    });
+
     it("should update that segment", () => {
       cy.visit("/admin");
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
diff --git a/e2e/test/scenarios/onboarding/home/browse.cy.spec.js b/e2e/test/scenarios/onboarding/home/browse.cy.spec.js
index 1e0aa6b4db17eb701f7df31fca983c4f535ddcf5..7d71b03153e20147636389e92b4ef1eb3eee6f15 100644
--- a/e2e/test/scenarios/onboarding/home/browse.cy.spec.js
+++ b/e2e/test/scenarios/onboarding/home/browse.cy.spec.js
@@ -14,6 +14,10 @@ describe("scenarios > browse data", () => {
     // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
     cy.findByText(/^Our data$/i);
     // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Learn about our data").click();
+    cy.location("pathname").should("eq", "/reference/databases");
+    cy.go("back");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
     cy.findByText("Sample Database").click();
     // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
     cy.findByText("Products").click();
diff --git a/e2e/test/scenarios/onboarding/reference/databases.cy.spec.js b/e2e/test/scenarios/onboarding/reference/databases.cy.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..a266900a89e83c13ec4756b972c4f64a7c21437f
--- /dev/null
+++ b/e2e/test/scenarios/onboarding/reference/databases.cy.spec.js
@@ -0,0 +1,111 @@
+import { popover, restore, startNewQuestion } from "e2e/support/helpers";
+
+describe("scenarios > reference > databases", () => {
+  beforeEach(() => {
+    restore();
+    cy.signInAsAdmin();
+  });
+
+  it("should see the listing", () => {
+    cy.visit("/reference/databases");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.contains("Sample Database");
+  });
+
+  xit("should let the user navigate to details", () => {
+    cy.visit("/reference/databases");
+    cy.contains("Sample Database").click();
+    cy.contains("Why this database is interesting");
+  });
+
+  it("should let an admin edit details about the database", () => {
+    cy.visit("/reference/databases/1");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.contains("Edit").click();
+    // Q - is there any cleaner way to get a nearby element without having to know the DOM?
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.contains("Description")
+      .parent()
+      .parent()
+      .find("textarea")
+      .type("A pretty ok store");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.contains("Save").click();
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.contains("A pretty ok store");
+  });
+
+  it("should let an admin start to edit and cancel without saving", () => {
+    cy.visit("/reference/databases/1");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.contains("Edit").click();
+    // Q - is there any cleaner way to get a nearby element without having to know the DOM?
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.contains("Why this")
+      .parent()
+      .parent()
+      .find("textarea")
+      .type("Turns out it's not");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.contains("Cancel").click();
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.contains("Turns out").should("have.length", 0);
+  });
+
+  it("should let an admin edit the database name", () => {
+    cy.visit("/reference/databases/1");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.contains("Edit").click();
+    cy.get(".wrapper input").clear().type("My definitely profitable business");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.contains("Save").click();
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.contains("My definitely profitable business");
+  });
+
+  describe("multiple databases sorting order", () => {
+    beforeEach(() => {
+      ["d", "b", "a", "c"].forEach(name => {
+        cy.addH2SampleDatabase({ name });
+      });
+    });
+
+    it.skip("should sort data reference database list (metabase#15598)", () => {
+      cy.visit("/browse");
+      checkReferenceDatabasesOrder();
+
+      cy.visit("/reference/databases/");
+      checkReferenceDatabasesOrder();
+    });
+
+    it("should sort databases in new UI based question data selection popover", () => {
+      checkQuestionSourceDatabasesOrder();
+    });
+
+    it.skip("should sort databases in new native question data selection popover", () => {
+      checkQuestionSourceDatabasesOrder("Native query");
+    });
+  });
+});
+
+function checkReferenceDatabasesOrder() {
+  cy.get("[class*=Card]").as("databaseCard").first().should("have.text", "a");
+  cy.get("@databaseCard").last().should("have.text", "Sample Database");
+}
+
+function checkQuestionSourceDatabasesOrder(question_type) {
+  // Last item is "Saved Questions" for UI based questions so we have to check for the one before that (-2), and the last one for "Native" (-1)
+  const lastDatabaseIndex = question_type === "Native query" ? -1 : -2;
+  const selector =
+    question_type === "Native query"
+      ? ".List-item-title"
+      : ".List-section-title";
+
+  startNewQuestion();
+  popover().within(() => {
+    cy.get(selector).as("databaseName").first().should("have.text", "a");
+    cy.get("@databaseName")
+      .eq(lastDatabaseIndex)
+      .should("have.text", "Sample Database");
+  });
+}
diff --git a/e2e/test/scenarios/onboarding/reference/metrics.cy.spec.js b/e2e/test/scenarios/onboarding/reference/metrics.cy.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..33a664fe33b455e29f545225fcccec965750438e
--- /dev/null
+++ b/e2e/test/scenarios/onboarding/reference/metrics.cy.spec.js
@@ -0,0 +1,101 @@
+import { restore } from "e2e/support/helpers";
+import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
+
+const { ORDERS, ORDERS_ID } = SAMPLE_DATABASE;
+
+describe("scenarios > reference > metrics", () => {
+  const METRIC_NAME = "orders < 100";
+  const METRIC_DESCRIPTION = "Count of orders with a total under $100.";
+
+  beforeEach(() => {
+    restore();
+    cy.signInAsAdmin();
+
+    cy.request("POST", "/api/metric", {
+      definition: {
+        aggregation: ["count"],
+        filter: ["<", ["field", ORDERS.TOTAL, null], 100],
+        "source-table": ORDERS_ID,
+      },
+      name: METRIC_NAME,
+      description: METRIC_DESCRIPTION,
+      table_id: ORDERS_ID,
+    });
+  });
+
+  it("should see the listing", () => {
+    cy.visit("/reference/metrics");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText(METRIC_NAME);
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText(METRIC_DESCRIPTION);
+  });
+
+  it("should let the user navigate to details", () => {
+    cy.visit("/reference/metrics");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText(METRIC_NAME).click();
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Why this metric is interesting");
+  });
+
+  it("should let an admin edit details about the metric", () => {
+    cy.visit("/reference/metrics");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText(METRIC_NAME).click();
+
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Edit").click();
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Description")
+      .parent()
+      .parent()
+      .find("textarea")
+      .clear()
+      .type("Count of orders under $100");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Save").click();
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Reason for changes")
+      .parent()
+      .parent()
+      .find("textarea")
+      .type("Renaming the description");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Save changes").click();
+
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Count of orders under $100");
+  });
+
+  it("should let an admin start to edit and cancel without saving", () => {
+    cy.visit("/reference/metrics");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText(METRIC_NAME).click();
+
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Edit").click();
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Why this metric is interesting")
+      .parent()
+      .parent()
+      .find("textarea")
+      .type("Because it's very nice");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Cancel").click();
+
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Because it's very nice").should("have.length", 0);
+  });
+
+  it("should have different URI while editing the metric", () => {
+    cy.visit("/reference/metrics");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText(METRIC_NAME).click();
+
+    cy.url().should("match", /\/reference\/metrics\/\d+$/);
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Edit").click();
+    cy.url().should("match", /\/reference\/metrics\/\d+\/edit$/);
+  });
+});
diff --git a/e2e/test/scenarios/onboarding/reference/reproductions/5276-remove-field-type.cy.spec.js b/e2e/test/scenarios/onboarding/reference/reproductions/5276-remove-field-type.cy.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..090e969169ed700afda49c09dc988ac2fa9817f5
--- /dev/null
+++ b/e2e/test/scenarios/onboarding/reference/reproductions/5276-remove-field-type.cy.spec.js
@@ -0,0 +1,33 @@
+import { popover, restore } from "e2e/support/helpers";
+
+describe("issue 5276", () => {
+  beforeEach(() => {
+    restore();
+    cy.signInAsAdmin();
+    cy.intercept("PUT", "/api/field/*").as("updateField");
+  });
+
+  it("should allow removing the field type (metabase#5276)", () => {
+    cy.visit("/reference/databases");
+
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Sample Database").click();
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Tables in Sample Database").click();
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Products").click();
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Fields in this table").click();
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Edit").click();
+
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Score").click();
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    popover().within(() => cy.findByText("No field type").click());
+    cy.button("Save").click();
+    cy.wait("@updateField");
+    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+    cy.findByText("Score").should("not.exist");
+  });
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/auth/components/SsoButton/SsoButton.unit.spec.tsx b/enterprise/frontend/src/metabase-enterprise/auth/components/SsoButton/SsoButton.unit.spec.tsx
index fb816ab68cdb4451e35c1777668e22ff8a07fc93..120b3f18c282d3a9f3da227a9d6ac9c99b055a94 100644
--- a/enterprise/frontend/src/metabase-enterprise/auth/components/SsoButton/SsoButton.unit.spec.tsx
+++ b/enterprise/frontend/src/metabase-enterprise/auth/components/SsoButton/SsoButton.unit.spec.tsx
@@ -21,7 +21,7 @@ const setup = () => {
   renderWithProviders(<SsoButton />, { storeInitialState: state });
 };
 
-describe("SsoButton", () => {
+describe("SSOButton", () => {
   it("should login immediately when embedded", async () => {
     jest.spyOn(domUtils, "redirect").mockImplementation(() => undefined);
 
diff --git a/frontend/src/metabase/browse/components/BrowseHeader.jsx b/frontend/src/metabase/browse/components/BrowseHeader.jsx
index 3a89d92b5a2a200172862f2885691561b27d68b7..fa626a75ee6fcc0da299e9bf04e1ae8e1e4f91ec 100644
--- a/frontend/src/metabase/browse/components/BrowseHeader.jsx
+++ b/frontend/src/metabase/browse/components/BrowseHeader.jsx
@@ -1,5 +1,9 @@
 /* eslint-disable react/prop-types */
+import { t } from "ttag";
+
 import BrowserCrumbs from "metabase/components/BrowserCrumbs";
+import { Icon } from "metabase/core/components/Icon";
+import Link from "metabase/core/components/Link";
 
 import { ANALYTICS_CONTEXT } from "metabase/browse/constants";
 import { BrowseHeaderContent, BrowseHeaderRoot } from "./BrowseHeader.styled";
@@ -9,6 +13,20 @@ export default function BrowseHeader({ crumbs }) {
     <BrowseHeaderRoot>
       <BrowseHeaderContent>
         <BrowserCrumbs crumbs={crumbs} analyticsContext={ANALYTICS_CONTEXT} />
+        <div className="flex flex-align-right">
+          <Link
+            className="flex flex-align-right"
+            to="reference"
+            data-metabase-event="NavBar;Reference"
+          >
+            <div className="flex align-center text-medium text-brand-hover">
+              <Icon className="flex align-center" size={14} name="reference" />
+              <span className="ml1 flex align-center text-bold">
+                {t`Learn about our data`}
+              </span>
+            </div>
+          </Link>
+        </div>
       </BrowseHeaderContent>
     </BrowseHeaderRoot>
   );
diff --git a/frontend/src/metabase/browse/components/TableBrowser/TableBrowser.jsx b/frontend/src/metabase/browse/components/TableBrowser/TableBrowser.jsx
index 3462916f83f582ab8f001af5c66180f2f8dfe374..4d8c033f3182e41b5d499f5fa85fbcbb95e9df61 100644
--- a/frontend/src/metabase/browse/components/TableBrowser/TableBrowser.jsx
+++ b/frontend/src/metabase/browse/components/TableBrowser/TableBrowser.jsx
@@ -135,6 +135,16 @@ const TableBrowserItemButtons = ({ tableId, dbId, xraysEnabled }) => {
           />
         </TableActionLink>
       )}
+      <TableActionLink
+        to={`/reference/databases/${dbId}/tables/${tableId}`}
+        data-metabase-event={`${ANALYTICS_CONTEXT};Table Item;Reference Click`}
+      >
+        <Icon
+          name="reference"
+          tooltip={t`Learn about this table`}
+          color={color("text-medium")}
+        />
+      </TableActionLink>
     </Fragment>
   );
 };
diff --git a/frontend/src/metabase/components/List/List.css b/frontend/src/metabase/components/List/List.css
new file mode 100644
index 0000000000000000000000000000000000000000..76c31959a1874b7285bdbc059668f3fcded85f9e
--- /dev/null
+++ b/frontend/src/metabase/components/List/List.css
@@ -0,0 +1,172 @@
+:root {
+  --title-color: var(--color-text-dark);
+  --subtitle-color: var(--color-text-medium);
+  --muted-color: var(--color-text-light);
+}
+
+:local(.list) {
+  composes: ml-auto mr-auto from "style";
+}
+
+:local(.list-wrapper) {
+  composes: ml-auto mr-auto from "style";
+}
+
+:local(.list) a {
+  text-decoration: none;
+}
+
+:local(.header) {
+  composes: flex flex-row from "style";
+  composes: mt4 mb2 from "style";
+  color: var(--title-color);
+  font-size: 24px;
+  min-height: 48px;
+}
+
+:local(.headerBody) {
+  composes: flex flex-full border-bottom from "style";
+  align-items: center;
+  height: 100%;
+  border-color: var(--color-brand);
+}
+
+:local(.headerLink) {
+  composes: text-brand ml2 flex-no-shrink from "style";
+  font-size: 14px;
+}
+
+:local(.headerButton) {
+  composes: flex ml1 align-center from "style";
+  font-size: 14px;
+}
+
+:local(.empty) {
+  composes: full flex justify-center from "style";
+  padding-top: 75px;
+}
+
+:local(.item) {
+  composes: flex align-center from "style";
+  composes: relative from "style";
+}
+
+:local(.itemBody) {
+  composes: flex-full from "style";
+  max-width: 100%;
+}
+
+:local(.itemTitle) {
+  composes: text-bold from "style";
+  max-width: 100%;
+  overflow: hidden;
+}
+
+:local(.itemName) {
+  composes: text-brand-hover mr1 from "style";
+  max-width: 100%;
+  overflow: hidden;
+}
+
+:local(.itemSubtitle) {
+  color: var(--subtitle-color);
+  max-width: 600px;
+  font-size: 14px;
+}
+
+:local(.itemSubtitleLight) {
+  composes: text-light from "style";
+  font-size: 14px;
+}
+
+:local(.itemSubtitleBold) {
+  color: var(--title-color);
+}
+
+:local(.icons) {
+  composes: flex flex-row align-center from "style";
+}
+:local(.leftIcons) {
+  composes: flex-no-shrink flex align-self-start mr2 from "style";
+  composes: icons;
+}
+:local(.rightIcons) {
+  composes: icons;
+}
+:local(.itemIcons) {
+  composes: leftIcons;
+  padding-top: 4px;
+}
+
+:local(.extraIcons) {
+  composes: icons;
+  composes: absolute top full-height from "style";
+  right: -40px;
+}
+
+/* hack fix for IE 11 which was hiding the archive icon */
+@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
+  :local(.extraIcons) {
+    composes: icons;
+  }
+}
+
+:local(.icon) {
+  composes: relative from "style";
+  color: var(--muted-color);
+}
+
+:local(.item) :local(.icon) {
+  visibility: hidden;
+}
+:local(.item):hover :local(.icon) {
+  visibility: visible;
+}
+:local(.icon):hover {
+  color: var(--color-brand);
+}
+
+/* ITEM CHECKBOX */
+:local(.itemCheckbox) {
+  composes: icon;
+  display: none;
+  visibility: visible !important;
+  margin-left: 10px;
+}
+:local(.item):hover :local(.itemCheckbox),
+:local(.item.selected) :local(.itemCheckbox) {
+  display: inline;
+}
+:local(.item.selected) :local(.itemCheckbox) {
+  color: var(--color-brand);
+}
+
+/* ITEM ICON */
+:local(.itemIcon) {
+  composes: icon;
+  visibility: visible !important;
+  composes: relative from "style";
+}
+:local(.item):hover :local(.itemIcon),
+:local(.item.selected) :local(.itemIcon) {
+  display: none;
+}
+
+/* CHART ICON */
+:local(.chartIcon) {
+  composes: icon;
+  visibility: visible !important;
+  composes: relative from "style";
+}
+
+/* ACTION ICONS */
+:local(.tagIcon),
+:local(.favoriteIcon),
+:local(.archiveIcon) {
+  composes: icon;
+  composes: mx1 from "style";
+}
+
+:local(.trigger) {
+  line-height: 0;
+}
diff --git a/frontend/src/metabase/components/List/List.jsx b/frontend/src/metabase/components/List/List.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..5d89949aaf7ff9229cc9f63103ee2265995bfa7c
--- /dev/null
+++ b/frontend/src/metabase/components/List/List.jsx
@@ -0,0 +1,13 @@
+/* eslint "react/prop-types": "warn" */
+import { memo } from "react";
+import PropTypes from "prop-types";
+
+import S from "./List.css";
+
+const List = ({ children }) => <ul className={S.list}>{children}</ul>;
+
+List.propTypes = {
+  children: PropTypes.any.isRequired,
+};
+
+export default memo(List);
diff --git a/frontend/src/metabase/components/List/index.jsx b/frontend/src/metabase/components/List/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..8e34bd39702766ae79f67c8e688c61074e185c3d
--- /dev/null
+++ b/frontend/src/metabase/components/List/index.jsx
@@ -0,0 +1 @@
+export { default } from "./List";
diff --git a/frontend/src/metabase/components/ListItem/ListItem.jsx b/frontend/src/metabase/components/ListItem/ListItem.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..4e0bc15f81120f8b716be95c7614d1e0ea9f42d6
--- /dev/null
+++ b/frontend/src/metabase/components/ListItem/ListItem.jsx
@@ -0,0 +1,49 @@
+/* eslint "react/prop-types": "warn" */
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { Link } from "react-router";
+import cx from "classnames";
+import Ellipsified from "metabase/core/components/Ellipsified";
+import Card from "metabase/components/Card";
+import S from "metabase/components/List/List.css";
+import { Icon } from "metabase/core/components/Icon";
+
+const ListItem = ({ name, description, placeholder, url, icon }) => (
+  <li className="relative">
+    <Link to={url} className="text-brand-hover">
+      <Card hoverable className="mb2 p3 bg-white rounded bordered">
+        <div className={cx(S.item)}>
+          <div className={S.itemIcons}>
+            {icon && <Icon className={S.chartIcon} name={icon} size={16} />}
+          </div>
+          <div className={S.itemBody}>
+            <div className={S.itemTitle}>
+              <Ellipsified
+                className={S.itemName}
+                tooltip={name}
+                tooltipMaxWidth="100%"
+              >
+                <h3>{name}</h3>
+              </Ellipsified>
+            </div>
+            {(description || placeholder) && (
+              <div className={cx(S.itemSubtitle)}>
+                {description || placeholder}
+              </div>
+            )}
+          </div>
+        </div>
+      </Card>
+    </Link>
+  </li>
+);
+
+ListItem.propTypes = {
+  name: PropTypes.string.isRequired,
+  url: PropTypes.string,
+  description: PropTypes.string,
+  placeholder: PropTypes.string,
+  icon: PropTypes.string,
+};
+
+export default memo(ListItem);
diff --git a/frontend/src/metabase/components/ListItem/ListItem.unit.spec.js b/frontend/src/metabase/components/ListItem/ListItem.unit.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..50939dd9dd745ab8478804df9f2944ddb2099aca
--- /dev/null
+++ b/frontend/src/metabase/components/ListItem/ListItem.unit.spec.js
@@ -0,0 +1,56 @@
+import { Route } from "react-router";
+import { screen, getIcon, renderWithProviders } from "__support__/ui";
+import ListItem from "./ListItem";
+
+const ITEM_NAME = "Table Foo";
+const ITEM_DESCRIPTION = "Nice table description.";
+
+function setup({ name, ...opts }) {
+  return renderWithProviders(
+    <Route path="/" component={() => <ListItem name={name} {...opts} />} />,
+    { withRouter: true },
+  );
+}
+
+describe("ListItem", () => {
+  it("should render", () => {
+    setup({
+      name: ITEM_NAME,
+      description: ITEM_DESCRIPTION,
+      icon: "table",
+      url: "/foo",
+    });
+
+    expect(screen.getByText(ITEM_NAME)).toBeInTheDocument();
+    expect(screen.getByText(ITEM_DESCRIPTION)).toBeInTheDocument();
+    expect(getIcon("table")).toBeInTheDocument();
+    expect(screen.getByRole("link")).toHaveProperty(
+      "href",
+      "http://localhost/foo",
+    );
+  });
+
+  it("should render with just the name", () => {
+    setup({ name: ITEM_NAME });
+    expect(screen.getByText(ITEM_NAME)).toBeInTheDocument();
+  });
+
+  it("should display the placeholder if there's no description", () => {
+    setup({ name: ITEM_NAME, placeholder: "Placeholder text" });
+
+    expect(screen.getByText(ITEM_NAME)).toBeInTheDocument();
+    expect(screen.getByText("Placeholder text")).toBeInTheDocument();
+  });
+
+  it("should display the description and omit the placeholder if both are present", () => {
+    setup({
+      name: ITEM_NAME,
+      description: ITEM_DESCRIPTION,
+      placeholder: "Placeholder text",
+    });
+
+    expect(screen.getByText(ITEM_NAME)).toBeInTheDocument();
+    expect(screen.getByText(ITEM_DESCRIPTION)).toBeInTheDocument();
+    expect(screen.queryByText("Placeholder text")).not.toBeInTheDocument();
+  });
+});
diff --git a/frontend/src/metabase/components/ListItem/index.jsx b/frontend/src/metabase/components/ListItem/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..3fb6c1bab362d14ef8abb0f5dcf765d0b1bd1d10
--- /dev/null
+++ b/frontend/src/metabase/components/ListItem/index.jsx
@@ -0,0 +1 @@
+export { default } from "./ListItem";
diff --git a/frontend/src/metabase/components/QueryButton/QueryButton.css b/frontend/src/metabase/components/QueryButton/QueryButton.css
new file mode 100644
index 0000000000000000000000000000000000000000..fdb2d0b5a9485b64b18b848231d45cd25bececb2
--- /dev/null
+++ b/frontend/src/metabase/components/QueryButton/QueryButton.css
@@ -0,0 +1,8 @@
+:local(.queryButton) {
+  composes: flex align-center no-decoration text-brand py1 from "style";
+}
+
+:local(.queryButtonText) {
+  composes: flex-full ml2 from "style";
+  max-width: 100%;
+}
diff --git a/frontend/src/metabase/components/QueryButton/QueryButton.jsx b/frontend/src/metabase/components/QueryButton/QueryButton.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..1a050d7cb8ec4d8321a3608e5e24a28bb21fe24f
--- /dev/null
+++ b/frontend/src/metabase/components/QueryButton/QueryButton.jsx
@@ -0,0 +1,30 @@
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { Link } from "react-router";
+import cx from "classnames";
+
+import { Icon } from "metabase/core/components/Icon";
+import S from "./QueryButton.css";
+
+const QueryButton = ({ className, text, icon, iconClass, onClick, link }) => (
+  <div className={className}>
+    <Link
+      className={cx(S.queryButton, "bg-light-hover px1 rounded")}
+      onClick={onClick}
+      to={link}
+    >
+      <Icon name={icon} />
+      <span className={S.queryButtonText}>{text}</span>
+    </Link>
+  </div>
+);
+QueryButton.propTypes = {
+  className: PropTypes.string,
+  icon: PropTypes.any.isRequired,
+  text: PropTypes.string.isRequired,
+  iconClass: PropTypes.string,
+  onClick: PropTypes.func,
+  link: PropTypes.string,
+};
+
+export default memo(QueryButton);
diff --git a/frontend/src/metabase/components/QueryButton/index.jsx b/frontend/src/metabase/components/QueryButton/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..9604a98ed97bad4b63af2ebd3e53a1d986b91675
--- /dev/null
+++ b/frontend/src/metabase/components/QueryButton/index.jsx
@@ -0,0 +1 @@
+export { default } from "./QueryButton";
diff --git a/frontend/src/metabase/entities/databases.js b/frontend/src/metabase/entities/databases.js
index fe9ff5b8f728cc6ae476da5ac919f657a1bc15f2..a3a7836daaf6cf1ce0de01961e837ed9cdb89784 100644
--- a/frontend/src/metabase/entities/databases.js
+++ b/frontend/src/metabase/entities/databases.js
@@ -1,10 +1,13 @@
 import _ from "underscore";
+import { normalize } from "normalizr";
 
 import { createSelector } from "@reduxjs/toolkit";
 import { createEntity } from "metabase/lib/entities";
 import * as Urls from "metabase/lib/urls";
 import { color } from "metabase/lib/colors";
 import {
+  fetchData,
+  createThunkAction,
   compose,
   withAction,
   withCachedDataAndRequestState,
@@ -41,6 +44,25 @@ const Databases = createEntity({
 
   // ACTION CREATORS
   objectActions: {
+    fetchDatabaseMetadata: createThunkAction(
+      FETCH_DATABASE_METADATA,
+      ({ id }, { reload = false, params } = {}) =>
+        (dispatch, getState) =>
+          fetchData({
+            dispatch,
+            getState,
+            requestStatePath: ["metadata", "databases", id],
+            existingStatePath: ["metadata", "databases", id],
+            getData: async () => {
+              const databaseMetadata = await MetabaseApi.db_metadata({
+                dbId: id,
+                ...params,
+              });
+              return normalize(databaseMetadata, DatabaseSchema);
+            },
+            reload,
+          }),
+    ),
     fetchIdFields: compose(
       withAction(FETCH_DATABASE_IDFIELDS),
       withCachedDataAndRequestState(
diff --git a/frontend/src/metabase/reducers-main.js b/frontend/src/metabase/reducers-main.js
index f0f6384b9e714c571a02cda6f2f15ee55a1fbb49..94e6730da8006d4a09cdc839f5d4652ae7014fb2 100644
--- a/frontend/src/metabase/reducers-main.js
+++ b/frontend/src/metabase/reducers-main.js
@@ -19,6 +19,9 @@ import * as parameters from "metabase/parameters/reducers";
 /* query builder */
 import * as qb from "metabase/query_builder/reducers";
 
+/* data reference */
+import reference from "metabase/reference/reference";
+
 /* revisions */
 import revisions from "metabase/redux/revisions";
 
@@ -43,6 +46,7 @@ export default {
   metabot: combineReducers(metabot),
   pulse: combineReducers(pulse),
   qb: combineReducers(qb),
+  reference,
   revisions,
   setup,
   admin,
diff --git a/frontend/src/metabase/redux/metadata.js b/frontend/src/metabase/redux/metadata.js
index 1479f69d9d29c30b81a3058df4bf95602e7a39c0..f517abcaedc0943cb848dff286d8f393be81fd71 100644
--- a/frontend/src/metabase/redux/metadata.js
+++ b/frontend/src/metabase/redux/metadata.js
@@ -1,3 +1,4 @@
+import { getIn } from "icepick";
 import _ from "underscore";
 import { createThunkAction, fetchData } from "metabase/lib/redux";
 
@@ -8,11 +9,79 @@ import { MetabaseApi, RevisionsApi } from "metabase/services";
 import Schemas from "metabase/entities/schemas";
 import Tables from "metabase/entities/tables";
 import Fields from "metabase/entities/fields";
+import Segments from "metabase/entities/segments";
+import Metrics from "metabase/entities/metrics";
+import Databases from "metabase/entities/databases";
 
 // NOTE: All of these actions are deprecated. Use metadata entities directly.
 
 const deprecated = message => console.warn("DEPRECATED: " + message);
 
+export const FETCH_METRICS = Metrics.actions.fetchList.toString();
+export const fetchMetrics = (reload = false) => {
+  deprecated("metabase/redux/metadata fetchMetrics");
+  return Metrics.actions.fetchList(null, { reload });
+};
+
+export const updateMetric = metric => {
+  deprecated("metabase/redux/metadata updateMetric");
+  return Metrics.actions.update(metric);
+};
+
+export const FETCH_SEGMENTS = Segments.actions.fetchList.toString();
+export const fetchSegments = (reload = false) => {
+  deprecated("metabase/redux/metadata fetchSegments");
+  return Segments.actions.fetchList(null, { reload });
+};
+
+export const updateSegment = segment => {
+  deprecated("metabase/redux/metadata updateSegment");
+  return Segments.actions.update(segment);
+};
+
+export const FETCH_REAL_DATABASES = Databases.actions.fetchList.toString();
+export const fetchRealDatabases = (reload = false) => {
+  deprecated("metabase/redux/metadata fetchRealDatabases");
+  return Databases.actions.fetchList({ include: "tables" }, { reload });
+};
+
+export const FETCH_DATABASE_METADATA =
+  Databases.actions.fetchDatabaseMetadata.toString();
+export const fetchDatabaseMetadata = (dbId, reload = false) => {
+  deprecated("metabase/redux/metadata fetchDatabaseMetadata");
+  return Databases.actions.fetchDatabaseMetadata({ id: dbId }, { reload });
+};
+
+export const updateDatabase = database => {
+  deprecated("metabase/redux/metadata updateDatabase");
+  const slimDatabase = _.omit(database, "tables", "tables_lookup");
+  return Databases.actions.update(slimDatabase);
+};
+
+export const updateTable = table => {
+  deprecated("metabase/redux/metadata updateTable");
+  const slimTable = _.omit(
+    table,
+    "fields",
+    "fields_lookup",
+    "aggregation_operators",
+    "metrics",
+    "segments",
+  );
+  return Tables.actions.update(slimTable);
+};
+
+export const fetchTables = (reload = false) => {
+  deprecated("metabase/redux/metadata fetchTables");
+  return Tables.actions.fetchList(null, { reload });
+};
+
+export { FETCH_TABLE_METADATA } from "metabase/entities/tables";
+export const fetchTableMetadata = (id, reload = false) => {
+  deprecated("metabase/redux/metadata fetchTableMetadata");
+  return Tables.actions.fetchMetadataAndForeignTables({ id }, { reload });
+};
+
 export const METADATA_FETCH_FIELD = "metabase/metadata/FETCH_FIELD";
 export const fetchField = createThunkAction(
   METADATA_FETCH_FIELD,
@@ -57,6 +126,27 @@ export const addFields = fieldMaps => {
   return Fields.actions.addFields(fieldMaps);
 };
 
+export const UPDATE_FIELD = Fields.actions.update.toString();
+export const updateField = field => {
+  deprecated("metabase/redux/metadata updateField");
+  const slimField = _.omit(field, "filter_operators_lookup");
+  return Fields.actions.update(slimField);
+};
+
+export const DELETE_FIELD_DIMENSION =
+  Fields.actions.deleteFieldDimension.toString();
+export const deleteFieldDimension = fieldId => {
+  deprecated("metabase/redux/metadata deleteFieldDimension");
+  return Fields.actions.deleteFieldDimension({ id: fieldId });
+};
+
+export const UPDATE_FIELD_DIMENSION =
+  Fields.actions.updateFieldDimension.toString();
+export const updateFieldDimension = (fieldId, dimension) => {
+  deprecated("metabase/redux/metadata updateFieldDimension");
+  return Fields.actions.updateFieldDimension({ id: fieldId }, dimension);
+};
+
 export const FETCH_REVISIONS = "metabase/metadata/FETCH_REVISIONS";
 export const fetchRevisions = createThunkAction(
   FETCH_REVISIONS,
@@ -84,6 +174,83 @@ export const fetchRevisions = createThunkAction(
   },
 );
 
+// for fetches with data dependencies in /reference
+export const FETCH_METRIC_TABLE = "metabase/metadata/FETCH_METRIC_TABLE";
+export const fetchMetricTable = createThunkAction(
+  FETCH_METRIC_TABLE,
+  (metricId, reload = false) => {
+    return async (dispatch, getState) => {
+      await dispatch(fetchMetrics()); // FIXME: fetchMetric?
+      const metric = getIn(getState(), ["entities", "metrics", metricId]);
+      const tableId = metric.table_id;
+      await dispatch(fetchTableMetadata(tableId));
+    };
+  },
+);
+
+export const FETCH_METRIC_REVISIONS =
+  "metabase/metadata/FETCH_METRIC_REVISIONS";
+export const fetchMetricRevisions = createThunkAction(
+  FETCH_METRIC_REVISIONS,
+  (metricId, reload = false) => {
+    return async (dispatch, getState) => {
+      await Promise.all([
+        dispatch(fetchRevisions("metric", metricId)),
+        dispatch(fetchMetrics()),
+      ]);
+      const metric = getIn(getState(), ["entities", "metrics", metricId]);
+      const tableId = metric.table_id;
+      await dispatch(fetchTableMetadata(tableId));
+    };
+  },
+);
+
+export const FETCH_SEGMENT_FIELDS = "metabase/metadata/FETCH_SEGMENT_FIELDS";
+export const fetchSegmentFields = createThunkAction(
+  FETCH_SEGMENT_FIELDS,
+  (segmentId, reload = false) => {
+    return async (dispatch, getState) => {
+      await dispatch(fetchSegments()); // FIXME: fetchSegment?
+      const segment = getIn(getState(), ["entities", "segments", segmentId]);
+      const tableId = segment.table_id;
+      await dispatch(fetchTableMetadata(tableId));
+      const table = getIn(getState(), ["entities", "tables", tableId]);
+      const databaseId = table.db_id;
+      await dispatch(fetchDatabaseMetadata(databaseId));
+    };
+  },
+);
+
+export const FETCH_SEGMENT_TABLE = "metabase/metadata/FETCH_SEGMENT_TABLE";
+export const fetchSegmentTable = createThunkAction(
+  FETCH_SEGMENT_TABLE,
+  (segmentId, reload = false) => {
+    return async (dispatch, getState) => {
+      await dispatch(fetchSegments()); // FIXME: fetchSegment?
+      const segment = getIn(getState(), ["entities", "segments", segmentId]);
+      const tableId = segment.table_id;
+      await dispatch(fetchTableMetadata(tableId));
+    };
+  },
+);
+
+export const FETCH_SEGMENT_REVISIONS =
+  "metabase/metadata/FETCH_SEGMENT_REVISIONS";
+export const fetchSegmentRevisions = createThunkAction(
+  FETCH_SEGMENT_REVISIONS,
+  (segmentId, reload = false) => {
+    return async (dispatch, getState) => {
+      await Promise.all([
+        dispatch(fetchRevisions("segment", segmentId)),
+        dispatch(fetchSegments()),
+      ]);
+      const segment = getIn(getState(), ["entities", "segments", segmentId]);
+      const tableId = segment.table_id;
+      await dispatch(fetchTableMetadata(tableId));
+    };
+  },
+);
+
 export const addRemappings = (fieldId, remappings) => {
   deprecated("metabase/redux/metadata addRemappings");
   return Fields.actions.addRemappings({ id: fieldId }, remappings);
@@ -124,6 +291,23 @@ export const fetchRemapping = createThunkAction(
   },
 );
 
+const FETCH_REAL_DATABASES_WITH_METADATA =
+  "metabase/metadata/FETCH_REAL_DATABASES_WITH_METADATA";
+export const fetchRealDatabasesWithMetadata = createThunkAction(
+  FETCH_REAL_DATABASES_WITH_METADATA,
+  (reload = false) => {
+    return async (dispatch, getState) => {
+      await dispatch(fetchRealDatabases());
+      const databases = getIn(getState(), ["entities", "databases"]);
+      await Promise.all(
+        Object.values(databases).map(database =>
+          dispatch(fetchDatabaseMetadata(database.id)),
+        ),
+      );
+    };
+  },
+);
+
 export const loadMetadataForQuery = (query, extraDependencies) =>
   loadMetadataForQueries([query], extraDependencies);
 
diff --git a/frontend/src/metabase/reference/Reference.css b/frontend/src/metabase/reference/Reference.css
new file mode 100644
index 0000000000000000000000000000000000000000..a263098939cf056e7f8077722845efc7da79a39f
--- /dev/null
+++ b/frontend/src/metabase/reference/Reference.css
@@ -0,0 +1,200 @@
+:root {
+  --title-color: var(--color-text-medium);
+  --subtitle-color: var(--color-text-medium);
+  --icon-width: 60px;
+}
+
+:local(.guideEmpty) {
+  composes: flex full justify-center from "style";
+  padding-top: 75px;
+}
+
+:local(.guideEmptyBody) {
+  composes: text-centered from "style";
+  max-width: 400px;
+}
+
+:local(.guideEmptyMessage) {
+  composes: text-dark text-paragraph text-centered mt3 from "style";
+}
+
+:local(.columnHeader) {
+  composes: flex flex-full from "style";
+  padding-top: 20px;
+  padding-bottom: 20px;
+}
+
+:local(.revisionsWrapper) {
+  padding-top: 20px;
+  padding-left: var(--icon-width);
+}
+
+:local(.schemaSeparator) {
+  composes: text-light mt2 from "style";
+  margin-left: var(--icon-width);
+  font-size: 18px;
+}
+
+:local(.tableActualName) {
+  font-family: "Lucida Console", Monaco, monospace;
+  font-size: 13px;
+  line-height: 1.4em;
+  letter-spacing: 1px;
+  white-space: pre-wrap;
+  color: var(--color-text-medium);
+  background-color: var(--color-bg-light);
+  border: 1px solid var(--color-text-light);
+  border-radius: 4px;
+  padding: 0.2em 0.4em;
+}
+
+:local(.guideLeftPadded) {
+  composes: flex full justify-center from "style";
+}
+
+:local(.guideLeftPadded)::before {
+  /*FIXME: not sure how to share this with other components
+     because we can't use composes here apparently. any workarounds?*/
+  content: "";
+  display: block;
+  flex: 0.3;
+  max-width: 250px;
+  margin-right: 50px;
+}
+
+:local(.guideLeftPaddedBody) {
+  flex: 0.7;
+  max-width: 550px;
+}
+
+:local(.guideWrapper) {
+  margin-bottom: 50px;
+}
+
+:local(.guideTitle) {
+  composes: guideLeftPadded;
+  font-size: 24px;
+  margin-top: 50px;
+}
+
+:local(.guideTitleBody) {
+  composes: full text-dark text-bold from "style";
+  composes: guideLeftPaddedBody;
+}
+
+:local(.guideSeeAll) {
+  composes: guideLeftPadded;
+  font-size: 18px;
+}
+
+:local(.guideSeeAllBody) {
+  composes: flex full text-dark text-bold mt4 from "style";
+  composes: guideLeftPaddedBody;
+}
+
+:local(.guideSeeAllLink) {
+  composes: flex-full block no-decoration py1 border-top from "style";
+}
+
+:local(.guideContact) {
+  composes: mt4 from "style";
+  composes: guideLeftPadded;
+  margin-bottom: 100px;
+}
+
+:local(.guideContactBody) {
+  composes: full from "style";
+  composes: guideLeftPaddedBody;
+  font-size: 16px;
+}
+
+:local(.guideEditHeader) {
+  composes: full text-body my4 from "style";
+  max-width: 550px;
+  color: var(--color-text-dark);
+}
+
+:local(.guideEditHeaderTitle) {
+  composes: text-bold mb2 from "style";
+  font-size: 24px;
+}
+
+:local(.guideEditCards) {
+  composes: mt2 mb4 from "style";
+}
+
+:local(.guideEditCard) {
+  composes: input p4 from "style";
+}
+
+:local(.guideEditLabel) {
+  composes: block text-bold mb2 from "style";
+  font-size: 16px;
+  color: var(--title-color);
+}
+
+:local(.guideEditHeaderDescription) {
+  font-size: 16px;
+}
+
+:local(.guideEditTitle) {
+  composes: block text-body text-bold from "style";
+  color: var(--title-color);
+  font-size: 16px;
+  margin-top: 50px;
+}
+
+:local(.guideEditSubtitle) {
+  composes: text-body from "style";
+  color: var(--color-text-light);
+  font-size: 16px;
+  max-width: 700px;
+}
+
+:local(.guideEditAddButton) {
+  composes: flex full my2 pl4 from "style";
+  padding-right: 3.5rem;
+}
+
+:local(.guideEditAddButton)::before {
+  content: "";
+  display: block;
+  flex: 250;
+  max-width: 250px;
+  margin-right: 50px;
+}
+
+:local(.guideEditAddButtonBody) {
+  flex: 550;
+  max-width: 550px;
+}
+
+:local(.guideEditTextarea) {
+  composes: text-dark input p2 from "style";
+  resize: none;
+  font-size: 16px;
+  width: 100%;
+  max-width: 850px;
+  min-height: 100px;
+}
+
+:local(.guideEditContact) {
+  composes: flex from "style";
+}
+
+:local(.guideEditContactName) {
+  flex: 250;
+  max-width: 250px;
+  margin-right: 50px;
+}
+
+:local(.guideEditContactEmail) {
+  flex: 550;
+  max-width: 550px;
+}
+
+:local(.guideEditInput) {
+  composes: full text-dark input p2 from "style";
+  font-size: 16px;
+  display: block;
+}
diff --git a/frontend/src/metabase/reference/components/Detail.css b/frontend/src/metabase/reference/components/Detail.css
new file mode 100644
index 0000000000000000000000000000000000000000..b15eac4ae763c912b482b4394b7cec7f470d04cb
--- /dev/null
+++ b/frontend/src/metabase/reference/components/Detail.css
@@ -0,0 +1,42 @@
+:root {
+  --title-color: var(--color-text-medium);
+  --muted-color: var(--color-text-light);
+  --blue-color: var(--color-brand);
+}
+
+:local(.detail) {
+  composes: flex align-center from "style";
+  composes: relative from "style";
+}
+
+:local(.detailBody) {
+  composes: pb4 from "style";
+  max-width: 900px;
+}
+
+:local(.detailTitle) {
+  composes: text-bold inline-block from "style";
+  color: var(--title-color);
+}
+
+:local(.detailSubtitle) {
+  composes: text-dark text-paragraph from "style";
+  white-space: pre-wrap;
+  font-size: 16px;
+  line-height: 24px;
+  padding-top: 6px;
+}
+
+:local(.detailSubtitleLight) {
+  composes: text-light from "style";
+  padding-top: 6px;
+}
+
+:local(.detailTextarea) {
+  composes: text-dark input p2 from "style";
+  resize: none;
+  font-size: 16px;
+  width: 100%;
+  min-height: 100px;
+  border-color: var(--color-text-light);
+}
diff --git a/frontend/src/metabase/reference/components/Detail.jsx b/frontend/src/metabase/reference/components/Detail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..114592434066afe64321aced467d187044dd7591
--- /dev/null
+++ b/frontend/src/metabase/reference/components/Detail.jsx
@@ -0,0 +1,67 @@
+/* eslint "react/prop-types": "warn" */
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { Link } from "react-router";
+import { t } from "ttag";
+import cx from "classnames";
+import S from "./Detail.css";
+
+const Detail = ({
+  name,
+  description,
+  placeholder,
+  subtitleClass,
+  url,
+  icon,
+  isEditing,
+  field,
+}) => (
+  <div className={cx(S.detail)}>
+    <div className={isEditing ? cx(S.detailBody, "flex-full") : S.detailBody}>
+      <div className={S.detailTitle}>
+        {url ? (
+          <Link to={url} className={S.detailName}>
+            {name}
+          </Link>
+        ) : (
+          <span className={S.detailName}>{name}</span>
+        )}
+      </div>
+      <div
+        className={cx(description ? S.detailSubtitle : S.detailSubtitleLight)}
+      >
+        {isEditing ? (
+          <textarea
+            className={S.detailTextarea}
+            name={field.name}
+            placeholder={placeholder}
+            onChange={field.onChange}
+            //FIXME: use initialValues from redux forms instead of default value
+            // to allow for reinitializing on cancel (see GettingStartedGuide.jsx)
+            defaultValue={description}
+          />
+        ) : (
+          <span className={subtitleClass}>
+            {description || placeholder || t`No description yet`}
+          </span>
+        )}
+        {isEditing && field.error && field.touched && (
+          <span className="text-error">{field.error}</span>
+        )}
+      </div>
+    </div>
+  </div>
+);
+
+Detail.propTypes = {
+  name: PropTypes.string.isRequired,
+  url: PropTypes.string,
+  description: PropTypes.string,
+  placeholder: PropTypes.string,
+  subtitleClass: PropTypes.string,
+  icon: PropTypes.string,
+  isEditing: PropTypes.bool,
+  field: PropTypes.object,
+};
+
+export default memo(Detail);
diff --git a/frontend/src/metabase/reference/components/EditHeader.css b/frontend/src/metabase/reference/components/EditHeader.css
new file mode 100644
index 0000000000000000000000000000000000000000..b0d19554dc7288bf97662ea1c993227492975f87
--- /dev/null
+++ b/frontend/src/metabase/reference/components/EditHeader.css
@@ -0,0 +1,31 @@
+:root {
+  --edit-header-color: var(--color-brand);
+}
+
+:local(.editHeader) {
+  composes: text-white flex align-center from "style";
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 43px;
+  background-color: color-mod(var(--color-bg-white) alpha(-85%));
+}
+
+:local(.editHeaderButtons) {
+  composes: flex-align-right from "style";
+}
+
+:local(.editHeaderButton) {
+  border: none;
+  color: var(--edit-header-color);
+}
+
+:local(.saveButton) {
+  composes: editHeaderButton;
+}
+
+:local(.cancelButton) {
+  composes: editHeaderButton;
+  opacity: 0.5;
+}
diff --git a/frontend/src/metabase/reference/components/EditHeader.jsx b/frontend/src/metabase/reference/components/EditHeader.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..fade39a5093e38f5108194277a3fb9b48c19e6e7
--- /dev/null
+++ b/frontend/src/metabase/reference/components/EditHeader.jsx
@@ -0,0 +1,82 @@
+import { memo } from "react";
+import PropTypes from "prop-types";
+import cx from "classnames";
+import { t } from "ttag";
+import RevisionMessageModal from "metabase/reference/components/RevisionMessageModal";
+import S from "./EditHeader.css";
+
+const EditHeader = ({
+  hasRevisionHistory,
+  endEditing,
+  reinitializeForm = () => undefined,
+  submitting,
+  onSubmit,
+  revisionMessageFormField,
+}) => (
+  <div className={cx("EditHeader wrapper py1 px3", S.editHeader)}>
+    <div>{t`You are editing this page`}</div>
+    <div className={S.editHeaderButtons}>
+      <button
+        type="button"
+        className={cx(
+          "Button",
+          "Button--white",
+          "Button--small",
+          S.cancelButton,
+        )}
+        onClick={() => {
+          endEditing();
+          reinitializeForm();
+        }}
+      >
+        {t`Cancel`}
+      </button>
+
+      {hasRevisionHistory ? (
+        <RevisionMessageModal
+          action={() => onSubmit()}
+          field={revisionMessageFormField}
+          submitting={submitting}
+        >
+          <button
+            className={cx(
+              "Button",
+              "Button--primary",
+              "Button--white",
+              "Button--small",
+              S.saveButton,
+            )}
+            type="button"
+            disabled={submitting}
+          >
+            {t`Save`}
+          </button>
+        </RevisionMessageModal>
+      ) : (
+        <button
+          className={cx(
+            "Button",
+            "Button--primary",
+            "Button--white",
+            "Button--small",
+            S.saveButton,
+          )}
+          type="submit"
+          disabled={submitting}
+        >
+          {t`Save`}
+        </button>
+      )}
+    </div>
+  </div>
+);
+EditHeader.propTypes = {
+  hasRevisionHistory: PropTypes.bool,
+  endEditing: PropTypes.func.isRequired,
+  reinitializeForm: PropTypes.func,
+  submitting: PropTypes.bool.isRequired,
+  onSubmit: PropTypes.func,
+  revisionMessageFormField: PropTypes.object,
+};
+
+export default memo(EditHeader);
diff --git a/frontend/src/metabase/reference/components/EditableReferenceHeader.jsx b/frontend/src/metabase/reference/components/EditableReferenceHeader.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..295c0e6918111550aaadfa1c6c09a8fb2bef47fc
--- /dev/null
+++ b/frontend/src/metabase/reference/components/EditableReferenceHeader.jsx
@@ -0,0 +1,110 @@
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { Link } from "react-router";
+import cx from "classnames";
+import { t } from "ttag";
+import L from "metabase/components/List/List.css";
+
+import { Icon } from "metabase/core/components/Icon";
+import InputBlurChange from "metabase/components/InputBlurChange";
+import Ellipsified from "metabase/core/components/Ellipsified";
+import Button from "metabase/core/components/Button";
+import S from "./ReferenceHeader.css";
+
+const EditableReferenceHeader = ({
+  entity = {},
+  table,
+  type,
+  headerIcon,
+  headerLink,
+  name,
+  user,
+  isEditing,
+  hasSingleSchema,
+  hasDisplayName,
+  startEditing,
+  displayNameFormField,
+  nameFormField,
+}) => (
+  <div className="wrapper">
+    <div className={cx("relative", L.header)}>
+      <div className="flex align-center mr1">
+        {headerIcon && (
+          <Icon className="text-light" name={headerIcon} size={21} />
+        )}
+      </div>
+      {type === "table" && !hasSingleSchema && !isEditing && (
+        <div className={S.headerSchema}>{entity.schema}</div>
+      )}
+      <div
+        className={S.headerBody}
+        style={
+          isEditing && name === "Details" ? { alignItems: "flex-start" } : {}
+        }
+      >
+        {isEditing && name === "Details" ? (
+          <InputBlurChange
+            className={S.headerTextInput}
+            type="text"
+            name={
+              hasDisplayName ? displayNameFormField.name : nameFormField.name
+            }
+            placeholder={entity.name}
+            onChange={
+              hasDisplayName
+                ? displayNameFormField.onChange
+                : nameFormField.onChange
+            }
+            defaultValue={hasDisplayName ? entity.display_name : entity.name}
+          />
+        ) : (
+          [
+            <Ellipsified
+              key="1"
+              className={!headerLink && "flex-full"}
+              tooltipMaxWidth="100%"
+            >
+              {name === "Details"
+                ? hasDisplayName
+                  ? entity.display_name || entity.name
+                  : entity.name
+                : name}
+            </Ellipsified>,
+            headerLink && (
+              <Button
+                primary
+                className="flex flex-align-right mr2"
+                style={{ fontSize: 14 }}
+                data-metabase-event={`Data Reference;Entity -> QB click;${type}`}
+              >
+                <Link to={headerLink}>{t`See this ${type}`}</Link>
+              </Button>
+            ),
+          ]
+        )}
+        {user && user.is_superuser && !isEditing && (
+          <Button icon="pencil" style={{ fontSize: 14 }} onClick={startEditing}>
+            {t`Edit`}
+          </Button>
+        )}
+      </div>
+    </div>
+  </div>
+);
+EditableReferenceHeader.propTypes = {
+  entity: PropTypes.object,
+  table: PropTypes.object,
+  type: PropTypes.string,
+  headerIcon: PropTypes.string,
+  headerLink: PropTypes.string,
+  name: PropTypes.string,
+  user: PropTypes.object,
+  isEditing: PropTypes.bool,
+  hasSingleSchema: PropTypes.bool,
+  hasDisplayName: PropTypes.bool,
+  startEditing: PropTypes.func,
+  displayNameFormField: PropTypes.object,
+  nameFormField: PropTypes.object,
+};
+
+export default memo(EditableReferenceHeader);
diff --git a/frontend/src/metabase/reference/components/Field.css b/frontend/src/metabase/reference/components/Field.css
new file mode 100644
index 0000000000000000000000000000000000000000..c5087edd07223b42cbd946380b4f2e08b88fc31f
--- /dev/null
+++ b/frontend/src/metabase/reference/components/Field.css
@@ -0,0 +1,56 @@
+:root {
+  --title-color: var(--color-text-medium);
+}
+
+:local(.field) {
+  composes: flex align-center from "style";
+}
+
+:local(.fieldNameTitle) {
+  composes: flex-half pr2 from "style";
+}
+
+:local(.fieldName) {
+  composes: fieldNameTitle;
+}
+
+:local(.fieldNameTextInput) {
+  composes: input p1 from "style";
+  color: var(--title-color);
+  width: 100%;
+  font-size: 14px;
+}
+
+:local(.fieldSelect) {
+  composes: input p1 block from "style";
+}
+
+:local(.fieldType) {
+  composes: flex-1-quarter text-medium pr2 from "style";
+  overflow: hidden;
+  white-space: nowrap;
+}
+
+:local(.fieldDataType) {
+  composes: flex-1-quarter text-medium from "style";
+}
+
+:local(.fieldSecondary) {
+  composes: field;
+  font-size: 13px;
+}
+
+:local(.fieldActualName) {
+  composes: fieldNameTitle;
+  composes: text-monospace text-light from "style";
+  font-size: 12px;
+  letter-spacing: 1px;
+}
+
+:local(.fieldForeignKey) {
+  composes: fieldType;
+}
+
+:local(.fieldOther) {
+  composes: fieldDataType;
+}
diff --git a/frontend/src/metabase/reference/components/Field.jsx b/frontend/src/metabase/reference/components/Field.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..6a752c62b8fd44a6629780ebfa120973b288605d
--- /dev/null
+++ b/frontend/src/metabase/reference/components/Field.jsx
@@ -0,0 +1,128 @@
+/* eslint "react/prop-types": "warn" */
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { Link } from "react-router";
+import { t } from "ttag";
+import { getIn } from "icepick";
+import cx from "classnames";
+import * as MetabaseCore from "metabase/lib/core";
+
+import S from "metabase/components/List/List.css";
+import Select from "metabase/core/components/Select";
+import { Icon } from "metabase/core/components/Icon";
+import { isTypeFK } from "metabase-lib/types/utils/isa";
+import F from "./Field.css";
+
+const Field = ({ field, foreignKeys, url, icon, isEditing, formField }) => (
+  <div className={cx(S.item, "pt1", "border-top")}>
+    <div className={S.itemBody} style={{ maxWidth: "100%", borderTop: "none" }}>
+      <div className={F.field}>
+        <div className={cx(S.itemTitle, F.fieldName)}>
+          {isEditing ? (
+            <input
+              className={F.fieldNameTextInput}
+              type="text"
+              placeholder={field.name}
+              {...formField.display_name}
+              defaultValue={field.display_name}
+            />
+          ) : (
+            <div>
+              <Link to={url}>
+                <span className="text-brand">{field.display_name}</span>
+                <span className={cx(F.fieldActualName, "ml2")}>
+                  {field.name}
+                </span>
+              </Link>
+            </div>
+          )}
+        </div>
+        <div className={F.fieldType}>
+          {isEditing ? (
+            <Select
+              name={formField.semantic_type.name}
+              placeholder={t`Select a field type`}
+              value={
+                formField.semantic_type.value !== undefined
+                  ? formField.semantic_type.value
+                  : field.semantic_type
+              }
+              onChange={formField.semantic_type.onChange}
+              options={MetabaseCore.field_semantic_types.concat({
+                id: null,
+                name: t`No field type`,
+                section: t`Other`,
+              })}
+              optionValueFn={o => o.id}
+              optionSectionFn={o => o.section}
+            />
+          ) : (
+            <div className="flex">
+              <div className={S.leftIcons}>
+                {icon && <Icon className={S.chartIcon} name={icon} size={20} />}
+              </div>
+              <span
+                className={
+                  getIn(MetabaseCore.field_semantic_types_map, [
+                    field.semantic_type,
+                    "name",
+                  ])
+                    ? "text-medium"
+                    : "text-light"
+                }
+              >
+                {getIn(MetabaseCore.field_semantic_types_map, [
+                  field.semantic_type,
+                  "name",
+                ]) || t`No field type`}
+              </span>
+            </div>
+          )}
+        </div>
+        <div className={F.fieldDataType}>{field.base_type}</div>
+      </div>
+      <div className={cx(S.itemSubtitle, F.fieldSecondary, { mt1: true })}>
+        <div className={F.fieldForeignKey}>
+          {isEditing
+            ? (isTypeFK(formField.semantic_type.value) ||
+                (isTypeFK(field.semantic_type) &&
+                  formField.semantic_type.value === undefined)) && (
+                <Select
+                  name={formField.fk_target_field_id.name}
+                  placeholder={t`Select a target`}
+                  value={
+                    formField.fk_target_field_id.value ||
+                    field.fk_target_field_id
+                  }
+                  onChange={formField.fk_target_field_id.onChange}
+                  options={Object.values(foreignKeys)}
+                  optionValueFn={o => o.id}
+                />
+              )
+            : isTypeFK(field.semantic_type) && (
+                <span>
+                  {getIn(foreignKeys, [field.fk_target_field_id, "name"])}
+                </span>
+              )}
+        </div>
+        <div className={F.fieldOther} />
+      </div>
+      {field.description && (
+        <div className={cx(S.itemSubtitle, "mb2", { mt1: isEditing })}>
+          {field.description}
+        </div>
+      )}
+    </div>
+  </div>
+);
+Field.propTypes = {
+  field: PropTypes.object.isRequired,
+  foreignKeys: PropTypes.object.isRequired,
+  url: PropTypes.string.isRequired,
+  placeholder: PropTypes.string,
+  icon: PropTypes.string,
+  isEditing: PropTypes.bool,
+  formField: PropTypes.object,
+};
+
+export default memo(Field);
diff --git a/frontend/src/metabase/reference/components/FieldToGroupBy.css b/frontend/src/metabase/reference/components/FieldToGroupBy.css
new file mode 100644
index 0000000000000000000000000000000000000000..81c761f56ada006c881607032c361ce89fcf8bb3
--- /dev/null
+++ b/frontend/src/metabase/reference/components/FieldToGroupBy.css
@@ -0,0 +1,5 @@
+:local(.fieldToGroupByText) {
+  composes: flex-full from "style";
+  font-size: 14px;
+  color: var(--color-text-medium);
+}
diff --git a/frontend/src/metabase/reference/components/FieldToGroupBy.jsx b/frontend/src/metabase/reference/components/FieldToGroupBy.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..aff73679ed42f2deb972455074fd4c461f88a678
--- /dev/null
+++ b/frontend/src/metabase/reference/components/FieldToGroupBy.jsx
@@ -0,0 +1,44 @@
+/* eslint-disable react/prop-types */
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { t } from "ttag";
+import cx from "classnames";
+import Q from "metabase/components/QueryButton/QueryButton.css";
+
+import { Icon } from "metabase/core/components/Icon";
+import S from "./FieldToGroupBy.css";
+
+const FieldToGroupBy = ({
+  className,
+  metric,
+  field,
+  icon,
+  iconClass,
+  onClick,
+  secondaryOnClick,
+}) => (
+  <div className={className}>
+    <a className={Q.queryButton} onClick={onClick}>
+      <div className={S.fieldToGroupByText}>
+        <div className="text-brand text-bold">{field.display_name}</div>
+      </div>
+      <Icon
+        className={cx(iconClass, "pr1")}
+        tooltip={field.description ? field.description : t`Look up this field`}
+        size={16}
+        name="reference"
+        onClick={secondaryOnClick}
+      />
+    </a>
+  </div>
+);
+FieldToGroupBy.propTypes = {
+  className: PropTypes.string,
+  metric: PropTypes.object.isRequired,
+  field: PropTypes.object.isRequired,
+  iconClass: PropTypes.string,
+  onClick: PropTypes.func,
+  secondaryOnClick: PropTypes.func,
+};
+
+export default memo(FieldToGroupBy);
diff --git a/frontend/src/metabase/reference/components/FieldTypeDetail.jsx b/frontend/src/metabase/reference/components/FieldTypeDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..da1b91d50da8105bb95106e22c735b14589e32e0
--- /dev/null
+++ b/frontend/src/metabase/reference/components/FieldTypeDetail.jsx
@@ -0,0 +1,89 @@
+import { memo } from "react";
+import PropTypes from "prop-types";
+import cx from "classnames";
+import { getIn } from "icepick";
+import { t } from "ttag";
+import * as MetabaseCore from "metabase/lib/core";
+
+import Select from "metabase/core/components/Select";
+
+import D from "metabase/reference/components/Detail.css";
+import { isTypeFK, isNumericBaseType } from "metabase-lib/types/utils/isa";
+
+const FieldTypeDetail = ({
+  field,
+  foreignKeys,
+  fieldTypeFormField,
+  foreignKeyFormField,
+  isEditing,
+}) => (
+  <div className={cx(D.detail)}>
+    <div className={D.detailBody}>
+      <div className={D.detailTitle}>
+        <span className={D.detailName}>{t`Field type`}</span>
+      </div>
+      <div className={cx(D.detailSubtitle, { mt1: true })}>
+        <span>
+          {isEditing ? (
+            <Select
+              placeholder={t`Select a field type`}
+              value={fieldTypeFormField.value || field.semantic_type}
+              options={MetabaseCore.field_semantic_types
+                .concat({
+                  id: null,
+                  name: t`No field type`,
+                  section: t`Other`,
+                })
+                .filter(type =>
+                  !isNumericBaseType(field)
+                    ? !(type.id && type.id.startsWith("timestamp_"))
+                    : true,
+                )}
+              optionValueFn={o => o.id}
+              onChange={({ target: { value } }) =>
+                fieldTypeFormField.onChange(value)
+              }
+            />
+          ) : (
+            <span>
+              {getIn(MetabaseCore.field_semantic_types_map, [
+                field.semantic_type,
+                "name",
+              ]) || t`No field type`}
+            </span>
+          )}
+        </span>
+        <span className="ml4">
+          {isEditing
+            ? (isTypeFK(fieldTypeFormField.value) ||
+                (isTypeFK(field.semantic_type) &&
+                  fieldTypeFormField.value === undefined)) && (
+                <Select
+                  placeholder={t`Select a foreign key`}
+                  value={foreignKeyFormField.value || field.fk_target_field_id}
+                  options={Object.values(foreignKeys)}
+                  onChange={({ target: { value } }) =>
+                    foreignKeyFormField.onChange(value)
+                  }
+                  optionValueFn={o => o.id}
+                />
+              )
+            : isTypeFK(field.semantic_type) && (
+                <span>
+                  {getIn(foreignKeys, [field.fk_target_field_id, "name"])}
+                </span>
+              )}
+        </span>
+      </div>
+    </div>
+  </div>
+);
+FieldTypeDetail.propTypes = {
+  field: PropTypes.object.isRequired,
+  foreignKeys: PropTypes.object.isRequired,
+  fieldTypeFormField: PropTypes.object.isRequired,
+  foreignKeyFormField: PropTypes.object.isRequired,
+  isEditing: PropTypes.bool.isRequired,
+};
+
+export default memo(FieldTypeDetail);
diff --git a/frontend/src/metabase/reference/components/FieldsToGroupBy.jsx b/frontend/src/metabase/reference/components/FieldsToGroupBy.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..d092531c9895b49c01c1dbceac55624f8421ba83
--- /dev/null
+++ b/frontend/src/metabase/reference/components/FieldsToGroupBy.jsx
@@ -0,0 +1,69 @@
+/* eslint-disable react/prop-types */
+import { Component } from "react";
+import { connect } from "react-redux";
+
+import D from "metabase/reference/components/Detail.css";
+import L from "metabase/components/List/List.css";
+
+import FieldToGroupBy from "metabase/reference/components/FieldToGroupBy";
+
+import { fetchTableMetadata } from "metabase/redux/metadata";
+import { getMetadata } from "metabase/selectors/metadata";
+import { getQuestionUrl } from "../utils";
+import S from "./UsefulQuestions.css";
+
+const mapDispatchToProps = {
+  fetchTableMetadata,
+};
+
+const mapStateToProps = (state, props) => ({
+  metadata: getMetadata(state, props),
+});
+
+class FieldsToGroupBy extends Component {
+  render() {
+    const { fields, databaseId, metric, title, onChangeLocation, metadata } =
+      this.props;
+
+    return (
+      <div>
+        <div className={D.detailBody}>
+          <div className={D.detailTitle}>
+            <span className={D.detailName}>{title}</span>
+          </div>
+          <div className={S.usefulQuestions}>
+            {fields &&
+              Object.values(fields).map((field, index, fields) => (
+                <FieldToGroupBy
+                  key={field.id}
+                  className="px1 mb1 rounded bg-light-hover"
+                  iconClass={L.icon}
+                  field={field}
+                  metric={metric}
+                  onClick={() =>
+                    onChangeLocation(
+                      getQuestionUrl({
+                        dbId: databaseId,
+                        tableId: field.table_id,
+                        fieldId: field.id,
+                        metricId: metric.id,
+                        metadata,
+                      }),
+                    )
+                  }
+                  secondaryOnClick={event => {
+                    event.stopPropagation();
+                    onChangeLocation(
+                      `/reference/databases/${databaseId}/tables/${field.table_id}/fields/${field.id}`,
+                    );
+                  }}
+                />
+              ))}
+          </div>
+        </div>
+      </div>
+    );
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(FieldsToGroupBy);
diff --git a/frontend/src/metabase/reference/components/Formula.css b/frontend/src/metabase/reference/components/Formula.css
new file mode 100644
index 0000000000000000000000000000000000000000..0978919ff9bb32859dde86ab26b7f6c4399e295a
--- /dev/null
+++ b/frontend/src/metabase/reference/components/Formula.css
@@ -0,0 +1,38 @@
+:local(.formula) {
+  composes: bordered rounded from "style";
+  background-color: var(--color-bg-light);
+  cursor: pointer;
+}
+
+:local(.formulaHeader) {
+  composes: flex align-center text-brand py1 px2 from "style";
+}
+
+:local(.formulaTitle) {
+  composes: ml2 from "style";
+  font-size: 16px;
+}
+
+:local(.formulaDefinitionInner) {
+  composes: p2 from "style";
+}
+
+.formulaDefinition {
+  overflow: hidden;
+}
+
+.formulaDefinition-enter {
+  max-height: 0px;
+}
+.formulaDefinition-enter.formulaDefinition-enter-active {
+  /* using 100% max-height breaks the transition */
+  max-height: 150px;
+  transition: max-height 300ms ease-out;
+}
+.formulaDefinition-exit {
+  max-height: 150px;
+}
+.formulaDefinition-exit.formulaDefinition-exit-active {
+  max-height: 0px;
+  transition: max-height 300ms ease-out;
+}
diff --git a/frontend/src/metabase/reference/components/Formula.jsx b/frontend/src/metabase/reference/components/Formula.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a764c11b829b184da4cbb5687b2b3caa9d41189d
--- /dev/null
+++ b/frontend/src/metabase/reference/components/Formula.jsx
@@ -0,0 +1,56 @@
+/* eslint-disable react/prop-types */
+import { Component } from "react";
+import cx from "classnames";
+import { connect } from "react-redux";
+import { t } from "ttag";
+import { TransitionGroup, CSSTransition } from "react-transition-group";
+
+import { Icon } from "metabase/core/components/Icon";
+
+import QueryDefinition from "metabase/query_builder/components/QueryDefinition";
+import { fetchTableMetadata } from "metabase/redux/metadata";
+import S from "./Formula.css";
+
+const mapDispatchToProps = {
+  fetchTableMetadata,
+};
+
+class Formula extends Component {
+  render() {
+    const { type, entity, isExpanded, expandFormula, collapseFormula } =
+      this.props;
+
+    return (
+      <div
+        className={cx(S.formula)}
+        onClick={isExpanded ? collapseFormula : expandFormula}
+      >
+        <div className={S.formulaHeader}>
+          <Icon name="beaker" size={24} />
+          <span className={S.formulaTitle}>{t`View the ${type} formula`}</span>
+        </div>
+        <TransitionGroup>
+          {isExpanded && (
+            <CSSTransition
+              key="formulaDefinition"
+              classNames="formulaDefinition"
+              timeout={{
+                enter: 300,
+                exit: 300,
+              }}
+            >
+              <div className="formulaDefinition">
+                <QueryDefinition
+                  className={S.formulaDefinitionInner}
+                  object={entity}
+                />
+              </div>
+            </CSSTransition>
+          )}
+        </TransitionGroup>
+      </div>
+    );
+  }
+}
+
+export default connect(null, mapDispatchToProps)(Formula);
diff --git a/frontend/src/metabase/reference/components/ReferenceHeader.css b/frontend/src/metabase/reference/components/ReferenceHeader.css
new file mode 100644
index 0000000000000000000000000000000000000000..3f8e18b66ce4456bc13fe582d16b2a5003bdcba3
--- /dev/null
+++ b/frontend/src/metabase/reference/components/ReferenceHeader.css
@@ -0,0 +1,42 @@
+:root {
+  --title-color: var(--color-text-medium);
+}
+
+:local(.headerBody) {
+  composes: flex flex-full text-dark text-bold from "style";
+  overflow: hidden;
+  align-items: center;
+  border-color: var(--color-border);
+}
+
+:local(.headerTextInput) {
+  composes: p1 from "style";
+  font-size: 18px;
+  color: var(--title-color);
+  max-width: 550px;
+}
+
+:local(.subheader) {
+  composes: mt1 mb2 from "style";
+}
+
+:local(.subheaderBody) {
+  composes: text-medium text-bold from "style";
+  font-size: 14px;
+}
+
+:local(.subheaderLink) {
+  color: var(--color-brand);
+  text-decoration: none;
+}
+
+:local(.subheaderLink):hover {
+  color: var(--color-brand);
+  transition: color 0.3s linear;
+}
+
+:local(.headerSchema) {
+  composes: text-light absolute from "style";
+  top: -10px;
+  font-size: 12px;
+}
diff --git a/frontend/src/metabase/reference/components/ReferenceHeader.jsx b/frontend/src/metabase/reference/components/ReferenceHeader.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..472f3cc5434b990b0be43e5b7d64678fed1a83b4
--- /dev/null
+++ b/frontend/src/metabase/reference/components/ReferenceHeader.jsx
@@ -0,0 +1,63 @@
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { Link } from "react-router";
+import cx from "classnames";
+
+import { t } from "ttag";
+import L from "metabase/components/List/List.css";
+
+import { Icon } from "metabase/core/components/Icon";
+import Ellipsified from "metabase/core/components/Ellipsified";
+import S from "./ReferenceHeader.css";
+
+const ReferenceHeader = ({
+  name,
+  type,
+  headerIcon,
+  headerBody,
+  headerLink,
+}) => (
+  <div className="wrapper">
+    <div className={cx("relative", L.header)}>
+      {headerIcon && (
+        <div className="flex align-center mr2">
+          <Icon className="text-light" name={headerIcon} size={21} />
+        </div>
+      )}
+      <div className={S.headerBody}>
+        <Ellipsified
+          key="1"
+          className={!headerLink && "flex-full"}
+          tooltipMaxWidth="100%"
+        >
+          {name}
+        </Ellipsified>
+
+        {headerLink && (
+          <div key="2" className={cx("flex-full", S.headerButton)}>
+            <Link
+              to={headerLink}
+              className={cx("Button", "Button--borderless", "ml3")}
+              data-metabase-event={`Data Reference;Entity -> QB click;${type}`}
+            >
+              <div className="flex align-center relative">
+                <span className="mr1 flex-no-shrink">{t`See this ${type}`}</span>
+                <Icon name="chevronright" size={16} />
+              </div>
+            </Link>
+          </div>
+        )}
+      </div>
+    </div>
+  </div>
+);
+
+ReferenceHeader.propTypes = {
+  name: PropTypes.string.isRequired,
+  type: PropTypes.string,
+  headerIcon: PropTypes.string,
+  headerBody: PropTypes.string,
+  headerLink: PropTypes.string,
+};
+
+export default memo(ReferenceHeader);
diff --git a/frontend/src/metabase/reference/components/RevisionMessageModal.css b/frontend/src/metabase/reference/components/RevisionMessageModal.css
new file mode 100644
index 0000000000000000000000000000000000000000..7c97265c891cbb0efc7fc948cac43b530e84196f
--- /dev/null
+++ b/frontend/src/metabase/reference/components/RevisionMessageModal.css
@@ -0,0 +1,13 @@
+:local(.modalBody) {
+  composes: flex justify-center align-center from "style";
+  padding-left: 32px;
+  padding-right: 32px;
+  padding-bottom: 32px;
+}
+
+:local(.modalTextArea) {
+  composes: flex-full text-dark input p2 from "style";
+  resize: none;
+  font-size: 16px;
+  min-height: 100px;
+}
diff --git a/frontend/src/metabase/reference/components/RevisionMessageModal.jsx b/frontend/src/metabase/reference/components/RevisionMessageModal.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..f494d302d783d75afd2b2b708f9494f9e7ad5e01
--- /dev/null
+++ b/frontend/src/metabase/reference/components/RevisionMessageModal.jsx
@@ -0,0 +1,64 @@
+/* eslint "react/prop-types": "warn" */
+import { createRef, Component } from "react";
+import PropTypes from "prop-types";
+import { t } from "ttag";
+import ModalWithTrigger from "metabase/components/ModalWithTrigger";
+import ModalContent from "metabase/components/ModalContent";
+
+import S from "./RevisionMessageModal.css";
+
+export default class RevisionMessageModal extends Component {
+  static propTypes = {
+    action: PropTypes.func.isRequired,
+    field: PropTypes.object.isRequired,
+    submitting: PropTypes.bool,
+    children: PropTypes.any,
+  };
+
+  constructor(props) {
+    super(props);
+
+    this.modal = createRef();
+  }
+
+  render() {
+    const { action, children, field, submitting } = this.props;
+
+    const onClose = () => {
+      this.modal.current.close();
+    };
+
+    const onAction = () => {
+      onClose();
+      action();
+    };
+
+    return (
+      <ModalWithTrigger ref={this.modal} triggerElement={children}>
+        <ModalContent title={t`Reason for changes`} onClose={onClose}>
+          <div className={S.modalBody}>
+            <textarea
+              className={S.modalTextArea}
+              placeholder={t`Leave a note to explain what changes you made and why they were required`}
+              {...field}
+            />
+          </div>
+
+          <div className="Form-actions">
+            <button
+              type="button"
+              className="Button Button--primary"
+              onClick={onAction}
+              disabled={submitting || field.error}
+            >{t`Save changes`}</button>
+            <button
+              type="button"
+              className="Button ml1"
+              onClick={onClose}
+            >{t`Cancel`}</button>
+          </div>
+        </ModalContent>
+      </ModalWithTrigger>
+    );
+  }
+}
diff --git a/frontend/src/metabase/reference/components/UsefulQuestions.css b/frontend/src/metabase/reference/components/UsefulQuestions.css
new file mode 100644
index 0000000000000000000000000000000000000000..f6ef86c10677b52f4224dfd24d39e592bfcd1e96
--- /dev/null
+++ b/frontend/src/metabase/reference/components/UsefulQuestions.css
@@ -0,0 +1,4 @@
+:local(.usefulQuestions) {
+  composes: text-brand mt1 from "style";
+  font-size: 16px;
+}
diff --git a/frontend/src/metabase/reference/components/UsefulQuestions.jsx b/frontend/src/metabase/reference/components/UsefulQuestions.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..6c2eac8a6c0f0f10c1a0c8e795af08a7f917f8dd
--- /dev/null
+++ b/frontend/src/metabase/reference/components/UsefulQuestions.jsx
@@ -0,0 +1,27 @@
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { t } from "ttag";
+import D from "metabase/reference/components/Detail.css";
+
+import QueryButton from "metabase/components/QueryButton";
+import S from "./UsefulQuestions.css";
+
+const UsefulQuestions = ({ questions }) => (
+  <div className={D.detail}>
+    <div className={D.detailBody}>
+      <div className={D.detailTitle}>
+        <span className={D.detailName}>{t`Potentially useful questions`}</span>
+      </div>
+      <div className={S.usefulQuestions}>
+        {questions.map((question, index, questions) => (
+          <QueryButton key={index} {...question} />
+        ))}
+      </div>
+    </div>
+  </div>
+);
+UsefulQuestions.propTypes = {
+  questions: PropTypes.array.isRequired,
+};
+
+export default memo(UsefulQuestions);
diff --git a/frontend/src/metabase/reference/databases/DatabaseDetail.jsx b/frontend/src/metabase/reference/databases/DatabaseDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..3f75ffe897cf8be69beef4b8d5902e2bdbb46341
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/DatabaseDetail.jsx
@@ -0,0 +1,175 @@
+/* eslint "react/prop-types": "warn" */
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { useFormik } from "formik";
+import { push } from "react-router-redux";
+import { t } from "ttag";
+import List from "metabase/components/List";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import EditHeader from "metabase/reference/components/EditHeader";
+import EditableReferenceHeader from "metabase/reference/components/EditableReferenceHeader";
+import Detail from "metabase/reference/components/Detail";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+import {
+  getDatabase,
+  getTable,
+  getFields,
+  getError,
+  getLoading,
+  getUser,
+  getIsEditing,
+  getIsFormulaExpanded,
+  getForeignKeys,
+} from "../selectors";
+
+const mapStateToProps = (state, props) => {
+  const entity = getDatabase(state, props) || {};
+  const fields = getFields(state, props);
+
+  return {
+    entity,
+    table: getTable(state, props),
+    metadataFields: fields,
+    loading: getLoading(state, props),
+    // naming this 'error' will conflict with redux form
+    loadingError: getError(state, props),
+    user: getUser(state, props),
+    foreignKeys: getForeignKeys(state, props),
+    isEditing: getIsEditing(state, props),
+    isFormulaExpanded: getIsFormulaExpanded(state, props),
+  };
+};
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+  onSubmit: actions.rUpdateDatabaseDetail,
+  onChangeLocation: push,
+};
+
+const propTypes = {
+  style: PropTypes.object.isRequired,
+  entity: PropTypes.object.isRequired,
+  table: PropTypes.object,
+  user: PropTypes.object.isRequired,
+  isEditing: PropTypes.bool,
+  startEditing: PropTypes.func.isRequired,
+  endEditing: PropTypes.func.isRequired,
+  startLoading: PropTypes.func.isRequired,
+  endLoading: PropTypes.func.isRequired,
+  setError: PropTypes.func.isRequired,
+  updateField: PropTypes.func.isRequired,
+  loading: PropTypes.bool,
+  loadingError: PropTypes.object,
+  onSubmit: PropTypes.func.isRequired,
+};
+
+const DatabaseDetail = props => {
+  const {
+    style,
+    entity,
+    table,
+    loadingError,
+    loading,
+    user,
+    isEditing,
+    startEditing,
+    endEditing,
+    onSubmit,
+  } = props;
+
+  const {
+    isSubmitting,
+    getFieldProps,
+    getFieldMeta,
+    handleSubmit,
+    handleReset,
+  } = useFormik({
+    initialValues: {},
+    onSubmit: fields => onSubmit(fields, { ...props, resetForm: handleReset }),
+  });
+
+  const getFormField = name => ({
+    ...getFieldProps(name),
+    ...getFieldMeta(name),
+  });
+
+  return (
+    <form style={style} className="full" onSubmit={handleSubmit}>
+      {isEditing && (
+        <EditHeader
+          hasRevisionHistory={false}
+          onSubmit={handleSubmit}
+          endEditing={endEditing}
+          reinitializeForm={handleReset}
+          submitting={isSubmitting}
+          revisionMessageFormField={getFormField("revision_message")}
+        />
+      )}
+      <EditableReferenceHeader
+        entity={entity}
+        table={table}
+        type="database"
+        name="Details"
+        headerIcon="database"
+        user={user}
+        isEditing={isEditing}
+        hasSingleSchema={false}
+        hasDisplayName={false}
+        startEditing={startEditing}
+        displayNameFormField={getFormField("display_name")}
+        nameFormField={getFormField("name")}
+      />
+      <LoadingAndErrorWrapper
+        loading={!loadingError && loading}
+        error={loadingError}
+      >
+        {() => (
+          <div className="wrapper">
+            <div className="pl4 pr3 pt4 mb4 mb1 bg-white rounded bordered">
+              <List>
+                <li className="relative">
+                  <Detail
+                    id="description"
+                    name={t`Description`}
+                    description={entity.description}
+                    placeholder={t`No description yet`}
+                    isEditing={isEditing}
+                    field={getFormField("description")}
+                  />
+                </li>
+                <li className="relative">
+                  <Detail
+                    id="points_of_interest"
+                    name={t`Why this database is interesting`}
+                    description={entity.points_of_interest}
+                    placeholder={t`Nothing interesting yet`}
+                    isEditing={isEditing}
+                    field={getFormField("points_of_interest")}
+                  />
+                </li>
+                <li className="relative">
+                  <Detail
+                    id="caveats"
+                    name={t`Things to be aware of about this database`}
+                    description={entity.caveats}
+                    placeholder={t`Nothing to be aware of yet`}
+                    isEditing={isEditing}
+                    field={getFormField("caveats")}
+                  />
+                </li>
+              </List>
+            </div>
+          </div>
+        )}
+      </LoadingAndErrorWrapper>
+    </form>
+  );
+};
+
+DatabaseDetail.propTypes = propTypes;
+
+export default connect(mapStateToProps, mapDispatchToProps)(DatabaseDetail);
diff --git a/frontend/src/metabase/reference/databases/DatabaseDetailContainer.jsx b/frontend/src/metabase/reference/databases/DatabaseDetailContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..1b7ed0a751ca6d9a1856994203ff10660db63200
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/DatabaseDetailContainer.jsx
@@ -0,0 +1,72 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+import DatabaseDetail from "metabase/reference/databases/DatabaseDetail";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import { getDatabase, getDatabaseId, getIsEditing } from "../selectors";
+import DatabaseSidebar from "./DatabaseSidebar";
+
+const mapStateToProps = (state, props) => ({
+  database: getDatabase(state, props),
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class DatabaseDetailContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    database: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchDatabaseMetadata(
+      this.props,
+      this.props.databaseId,
+    );
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { database, isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<DatabaseSidebar database={database} />}
+      >
+        <DatabaseDetail {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(DatabaseDetailContainer);
diff --git a/frontend/src/metabase/reference/databases/DatabaseList.jsx b/frontend/src/metabase/reference/databases/DatabaseList.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..7709ea9f22fe5a2348a48a5d40342cdc4fd15972
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/DatabaseList.jsx
@@ -0,0 +1,85 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { t } from "ttag";
+
+import S from "metabase/components/List/List.css";
+
+import List from "metabase/components/List";
+import ListItem from "metabase/components/ListItem";
+
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import * as metadataActions from "metabase/redux/metadata";
+import NoDatabasesEmptyState from "metabase/reference/databases/NoDatabasesEmptyState";
+import ReferenceHeader from "../components/ReferenceHeader";
+
+import { getDatabases, getError, getLoading } from "../selectors";
+
+const mapStateToProps = (state, props) => ({
+  entities: getDatabases(state, props),
+  loading: getLoading(state, props),
+  loadingError: getError(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+};
+
+class DatabaseList extends Component {
+  static propTypes = {
+    style: PropTypes.object.isRequired,
+    entities: PropTypes.object.isRequired,
+    loading: PropTypes.bool,
+    loadingError: PropTypes.object,
+  };
+
+  render() {
+    const { entities, style, loadingError, loading } = this.props;
+
+    const databases = Object.values(entities)
+      .filter(database => {
+        const exists = Boolean(database?.id && database?.name);
+        return exists && !database.is_saved_questions;
+      })
+      .sort((a, b) => {
+        const compared = a.name.localeCompare(b.name);
+        return compared !== 0 ? compared : a.engine.localeCompare(b.engine);
+      });
+
+    return (
+      <div style={style} className="full">
+        <ReferenceHeader name={t`Our data`} />
+        <LoadingAndErrorWrapper
+          loading={!loadingError && loading}
+          error={loadingError}
+        >
+          {() =>
+            Object.keys(entities).length > 0 ? (
+              <div className="wrapper">
+                <List>
+                  {databases.map(database => (
+                    <ListItem
+                      key={database.id}
+                      name={database.name}
+                      description={database.description}
+                      url={`/reference/databases/${database.id}`}
+                      icon="database"
+                    />
+                  ))}
+                </List>
+              </div>
+            ) : (
+              <div className={S.empty}>
+                <NoDatabasesEmptyState />
+              </div>
+            )
+          }
+        </LoadingAndErrorWrapper>
+      </div>
+    );
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(DatabaseList);
diff --git a/frontend/src/metabase/reference/databases/DatabaseListContainer.jsx b/frontend/src/metabase/reference/databases/DatabaseListContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..4290919c6d15d7cae3c0b998bc1f965bbcdd6ad2
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/DatabaseListContainer.jsx
@@ -0,0 +1,67 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import BaseSidebar from "metabase/reference/guide/BaseSidebar";
+import SidebarLayout from "metabase/components/SidebarLayout";
+import DatabaseList from "metabase/reference/databases/DatabaseList";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import { getDatabaseId, getIsEditing } from "../selectors";
+
+const mapStateToProps = (state, props) => ({
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class DatabaseListContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    location: PropTypes.object.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchDatabases(this.props);
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<BaseSidebar />}
+      >
+        <DatabaseList {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(DatabaseListContainer);
diff --git a/frontend/src/metabase/reference/databases/DatabaseSidebar.jsx b/frontend/src/metabase/reference/databases/DatabaseSidebar.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..201ea508bce7077bf0af8f5cf41b8be37357c464
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/DatabaseSidebar.jsx
@@ -0,0 +1,44 @@
+/* eslint "react/prop-types": "warn" */
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { t } from "ttag";
+import cx from "classnames";
+import S from "metabase/components/Sidebar.css";
+import Breadcrumbs from "metabase/components/Breadcrumbs";
+import SidebarItem from "metabase/components/SidebarItem";
+
+const DatabaseSidebar = ({ database, style, className }) => (
+  <div className={cx(S.sidebar, className)} style={style}>
+    <ul>
+      <div className={S.breadcrumbs}>
+        <Breadcrumbs
+          className="py4 ml3"
+          crumbs={[[t`Databases`, "/reference/databases"], [database.name]]}
+          inSidebar={true}
+          placeholder={t`Data Reference`}
+        />
+      </div>
+      <ol className="mx3">
+        <SidebarItem
+          key={`/reference/databases/${database.id}`}
+          href={`/reference/databases/${database.id}`}
+          icon="document"
+          name={t`Details`}
+        />
+        <SidebarItem
+          key={`/reference/databases/${database.id}/tables`}
+          href={`/reference/databases/${database.id}/tables`}
+          icon="table2"
+          name={t`Tables in ${database.name}`}
+        />
+      </ol>
+    </ul>
+  </div>
+);
+DatabaseSidebar.propTypes = {
+  database: PropTypes.object,
+  className: PropTypes.string,
+  style: PropTypes.object,
+};
+
+export default memo(DatabaseSidebar);
diff --git a/frontend/src/metabase/reference/databases/FieldDetail.jsx b/frontend/src/metabase/reference/databases/FieldDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..984214d072f54f309b0f353004f579ae95fb7ba1
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/FieldDetail.jsx
@@ -0,0 +1,267 @@
+/* eslint "react/prop-types": "warn" */
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { useFormik } from "formik";
+import { push } from "react-router-redux";
+import { t } from "ttag";
+import S from "metabase/reference/Reference.css";
+
+import List from "metabase/components/List";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import EditHeader from "metabase/reference/components/EditHeader";
+import EditableReferenceHeader from "metabase/reference/components/EditableReferenceHeader";
+import Detail from "metabase/reference/components/Detail";
+import FieldTypeDetail from "metabase/reference/components/FieldTypeDetail";
+import UsefulQuestions from "metabase/reference/components/UsefulQuestions";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+import { getQuestionUrl } from "../utils";
+
+import {
+  getDatabase,
+  getError,
+  getField,
+  getForeignKeys,
+  getIsEditing,
+  getIsFormulaExpanded,
+  getLoading,
+  getTable,
+  getUser,
+} from "../selectors";
+
+const interestingQuestions = (database, table, field, metadata) => {
+  return [
+    {
+      text: t`Number of ${table.display_name} grouped by ${field.display_name}`,
+      icon: "bar",
+      link: getQuestionUrl({
+        dbId: database.id,
+        tableId: table.id,
+        fieldId: field.id,
+        getCount: true,
+        visualization: "bar",
+        metadata,
+      }),
+    },
+    {
+      text: t`Number of ${table.display_name} grouped by ${field.display_name}`,
+      icon: "pie",
+      link: getQuestionUrl({
+        dbId: database.id,
+        tableId: table.id,
+        fieldId: field.id,
+        getCount: true,
+        visualization: "pie",
+        metadata,
+      }),
+    },
+    {
+      text: t`All distinct values of ${field.display_name}`,
+      icon: "table2",
+      link: getQuestionUrl({
+        dbId: database.id,
+        tableId: table.id,
+        fieldId: field.id,
+        metadata,
+      }),
+    },
+  ];
+};
+
+const mapStateToProps = (state, props) => {
+  const entity = getField(state, props) || {};
+
+  return {
+    entity,
+    field: entity,
+    table: getTable(state, props),
+    database: getDatabase(state, props),
+    loading: getLoading(state, props),
+    // naming this 'error' will conflict with redux form
+    loadingError: getError(state, props),
+    user: getUser(state, props),
+    foreignKeys: getForeignKeys(state, props),
+    isEditing: getIsEditing(state, props),
+    isFormulaExpanded: getIsFormulaExpanded(state, props),
+  };
+};
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+  onSubmit: actions.rUpdateFieldDetail,
+  onChangeLocation: push,
+};
+
+const propTypes = {
+  style: PropTypes.object.isRequired,
+  entity: PropTypes.object.isRequired,
+  field: PropTypes.object.isRequired,
+  table: PropTypes.object,
+  user: PropTypes.object.isRequired,
+  database: PropTypes.object.isRequired,
+  foreignKeys: PropTypes.object,
+  isEditing: PropTypes.bool,
+  startEditing: PropTypes.func.isRequired,
+  endEditing: PropTypes.func.isRequired,
+  startLoading: PropTypes.func.isRequired,
+  endLoading: PropTypes.func.isRequired,
+  setError: PropTypes.func.isRequired,
+  updateField: PropTypes.func.isRequired,
+  loading: PropTypes.bool,
+  loadingError: PropTypes.object,
+  metadata: PropTypes.object,
+  onSubmit: PropTypes.func.isRequired,
+};
+
+const FieldDetail = props => {
+  const {
+    style,
+    entity,
+    table,
+    loadingError,
+    loading,
+    user,
+    foreignKeys,
+    isEditing,
+    startEditing,
+    endEditing,
+    metadata,
+    onSubmit,
+  } = props;
+
+  const {
+    isSubmitting,
+    getFieldProps,
+    getFieldMeta,
+    handleSubmit,
+    handleReset,
+  } = useFormik({
+    initialValues: {},
+    onSubmit: fields => onSubmit(fields, { ...props, resetForm: handleReset }),
+  });
+
+  const getFormField = name => ({
+    ...getFieldProps(name),
+    ...getFieldMeta(name),
+  });
+
+  return (
+    <form style={style} className="full" onSubmit={handleSubmit}>
+      {isEditing && (
+        <EditHeader
+          hasRevisionHistory={false}
+          onSubmit={handleSubmit}
+          endEditing={endEditing}
+          reinitializeForm={handleReset}
+          submitting={isSubmitting}
+          revisionMessageFormField={getFormField("revision_message")}
+        />
+      )}
+      <EditableReferenceHeader
+        entity={entity}
+        table={table}
+        type="field"
+        headerIcon="field"
+        name="Details"
+        user={user}
+        isEditing={isEditing}
+        hasSingleSchema={false}
+        hasDisplayName={true}
+        startEditing={startEditing}
+        displayNameFormField={getFormField("display_name")}
+        nameFormField={getFormField("name")}
+      />
+      <LoadingAndErrorWrapper
+        loading={!loadingError && loading}
+        error={loadingError}
+      >
+        {() => (
+          <div className="wrapper">
+            <div className="pl4 pr3 pt4 mb4 mb1 bg-white rounded bordered">
+              <List>
+                <li className="relative">
+                  <Detail
+                    id="description"
+                    name={t`Description`}
+                    description={entity.description}
+                    placeholder={t`No description yet`}
+                    isEditing={isEditing}
+                    field={getFormField("description")}
+                  />
+                </li>
+                {!isEditing && (
+                  <li className="relative">
+                    <Detail
+                      id="name"
+                      name={t`Actual name in database`}
+                      description={entity.name}
+                      subtitleClass={S.tableActualName}
+                    />
+                  </li>
+                )}
+                <li className="relative">
+                  <Detail
+                    id="points_of_interest"
+                    name={t`Why this field is interesting`}
+                    description={entity.points_of_interest}
+                    placeholder={t`Nothing interesting yet`}
+                    isEditing={isEditing}
+                    field={getFormField("points_of_interest")}
+                  />
+                </li>
+                <li className="relative">
+                  <Detail
+                    id="caveats"
+                    name={t`Things to be aware of about this field`}
+                    description={entity.caveats}
+                    placeholder={t`Nothing to be aware of yet`}
+                    isEditing={isEditing}
+                    field={getFormField("caveats")}
+                  />
+                </li>
+
+                {!isEditing && (
+                  <li className="relative">
+                    <Detail
+                      id="base_type"
+                      name={t`Data type`}
+                      description={entity.base_type}
+                    />
+                  </li>
+                )}
+                <li className="relative">
+                  <FieldTypeDetail
+                    field={entity}
+                    foreignKeys={foreignKeys}
+                    fieldTypeFormField={getFormField("semantic_type")}
+                    foreignKeyFormField={getFormField("fk_target_field_id")}
+                    isEditing={isEditing}
+                  />
+                </li>
+                {!isEditing && (
+                  <li className="relative">
+                    <UsefulQuestions
+                      questions={interestingQuestions(
+                        props.database,
+                        props.table,
+                        props.field,
+                        metadata,
+                      )}
+                    />
+                  </li>
+                )}
+              </List>
+            </div>
+          </div>
+        )}
+      </LoadingAndErrorWrapper>
+    </form>
+  );
+};
+
+FieldDetail.propTypes = propTypes;
+
+export default connect(mapStateToProps, mapDispatchToProps)(FieldDetail);
diff --git a/frontend/src/metabase/reference/databases/FieldDetailContainer.jsx b/frontend/src/metabase/reference/databases/FieldDetailContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..00ab2e200954b5adf5df294d272d75d8754eafe5
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/FieldDetailContainer.jsx
@@ -0,0 +1,87 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+import FieldDetail from "metabase/reference/databases/FieldDetail";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+import { getMetadata } from "metabase/selectors/metadata";
+
+import {
+  getDatabase,
+  getTable,
+  getField,
+  getDatabaseId,
+  getIsEditing,
+} from "../selectors";
+import FieldSidebar from "./FieldSidebar";
+
+const mapStateToProps = (state, props) => ({
+  database: getDatabase(state, props),
+  table: getTable(state, props),
+  field: getField(state, props),
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+  metadata: getMetadata(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class FieldDetailContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    database: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    table: PropTypes.object.isRequired,
+    field: PropTypes.object.isRequired,
+    isEditing: PropTypes.bool,
+    metadata: PropTypes.object,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchDatabaseMetadata(
+      this.props,
+      this.props.databaseId,
+    );
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { database, table, field, isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={
+          <FieldSidebar database={database} table={table} field={field} />
+        }
+      >
+        <FieldDetail {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(FieldDetailContainer);
diff --git a/frontend/src/metabase/reference/databases/FieldList.jsx b/frontend/src/metabase/reference/databases/FieldList.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..454cb33746d9239e256c4cecd8bc86f8d8ae0bdb
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/FieldList.jsx
@@ -0,0 +1,194 @@
+/* eslint "react/prop-types": "warn" */
+/* eslint-disable react/no-unknown-property */
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { useFormik } from "formik";
+import { t } from "ttag";
+import cx from "classnames";
+import S from "metabase/components/List/List.css";
+import R from "metabase/reference/Reference.css";
+import F from "metabase/reference/components/Field.css";
+
+import Field from "metabase/reference/components/Field";
+import List from "metabase/components/List";
+import EmptyState from "metabase/components/EmptyState";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import EditHeader from "metabase/reference/components/EditHeader";
+import EditableReferenceHeader from "metabase/reference/components/EditableReferenceHeader";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+import { getIconForField } from "metabase-lib/metadata/utils/fields";
+import {
+  getError,
+  getFieldsByTable,
+  getForeignKeys,
+  getIsEditing,
+  getLoading,
+  getTable,
+  getUser,
+} from "../selectors";
+
+const emptyStateData = {
+  message: t`Fields in this table will appear here as they're added`,
+  icon: "fields",
+};
+
+const mapStateToProps = (state, props) => {
+  const data = getFieldsByTable(state, props);
+  return {
+    table: getTable(state, props),
+    entities: data,
+    foreignKeys: getForeignKeys(state, props),
+    loading: getLoading(state, props),
+    loadingError: getError(state, props),
+    user: getUser(state, props),
+    isEditing: getIsEditing(state, props),
+  };
+};
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+  onSubmit: actions.rUpdateFields,
+};
+
+const propTypes = {
+  style: PropTypes.object.isRequired,
+  entities: PropTypes.object.isRequired,
+  foreignKeys: PropTypes.object.isRequired,
+  isEditing: PropTypes.bool,
+  startEditing: PropTypes.func.isRequired,
+  endEditing: PropTypes.func.isRequired,
+  startLoading: PropTypes.func.isRequired,
+  endLoading: PropTypes.func.isRequired,
+  setError: PropTypes.func.isRequired,
+  updateField: PropTypes.func.isRequired,
+  user: PropTypes.object.isRequired,
+  table: PropTypes.object.isRequired,
+  loading: PropTypes.bool,
+  loadingError: PropTypes.object,
+  onSubmit: PropTypes.func.isRequired,
+  "data-testid": PropTypes.string,
+};
+
+const FieldList = props => {
+  const {
+    style,
+    entities,
+    foreignKeys,
+    table,
+    loadingError,
+    loading,
+    user,
+    isEditing,
+    startEditing,
+    endEditing,
+    onSubmit,
+  } = props;
+
+  const {
+    isSubmitting,
+    getFieldProps,
+    getFieldMeta,
+    handleSubmit,
+    handleReset,
+  } = useFormik({
+    initialValues: {},
+    onSubmit: fields =>
+      onSubmit(entities, fields, { ...props, resetForm: handleReset }),
+  });
+
+  const getFormField = name => ({
+    ...getFieldProps(name),
+    ...getFieldMeta(name),
+  });
+
+  const getNestedFormField = id => ({
+    display_name: getFormField(`${id}.display_name`),
+    semantic_type: getFormField(`${id}.semantic_type`),
+    fk_target_field_id: getFormField(`${id}.fk_target_field_id`),
+  });
+
+  return (
+    <form
+      style={style}
+      className="full"
+      onSubmit={handleSubmit}
+      testID={props["data-testid"]}
+    >
+      {isEditing && (
+        <EditHeader
+          hasRevisionHistory={false}
+          reinitializeForm={handleReset}
+          endEditing={endEditing}
+          submitting={isSubmitting}
+        />
+      )}
+      <EditableReferenceHeader
+        headerIcon="table2"
+        name={t`Fields in ${table.display_name}`}
+        user={user}
+        isEditing={isEditing}
+        startEditing={startEditing}
+      />
+      <LoadingAndErrorWrapper
+        loading={!loadingError && loading}
+        error={loadingError}
+      >
+        {() =>
+          Object.keys(entities).length > 0 ? (
+            <div className="wrapper">
+              <div className="pl4 pb2 mb4 bg-white rounded bordered">
+                <div className={S.item}>
+                  <div className={R.columnHeader}>
+                    <div className={cx(S.itemTitle, F.fieldNameTitle)}>
+                      {t`Field name`}
+                    </div>
+                    <div className={cx(S.itemTitle, F.fieldType)}>
+                      {t`Field type`}
+                    </div>
+                    <div className={cx(S.itemTitle, F.fieldDataType)}>
+                      {t`Data type`}
+                    </div>
+                  </div>
+                </div>
+                <List>
+                  {Object.values(entities)
+                    // respect the column sort order
+                    .sort((a, b) => a.position - b.position)
+                    .map(
+                      entity =>
+                        entity &&
+                        entity.id &&
+                        entity.name && (
+                          <li key={entity.id}>
+                            <Field
+                              field={entity}
+                              foreignKeys={foreignKeys}
+                              url={`/reference/databases/${table.db_id}/tables/${table.id}/fields/${entity.id}`}
+                              icon={getIconForField(entity)}
+                              isEditing={isEditing}
+                              formField={getNestedFormField(entity.id)}
+                            />
+                          </li>
+                        ),
+                    )}
+                </List>
+              </div>
+            </div>
+          ) : (
+            <div className={S.empty}>
+              <EmptyState {...emptyStateData} />
+            </div>
+          )
+        }
+      </LoadingAndErrorWrapper>
+    </form>
+  );
+};
+
+FieldList.propTypes = propTypes;
+
+export default connect(mapStateToProps, mapDispatchToProps)(FieldList);
diff --git a/frontend/src/metabase/reference/databases/FieldListContainer.jsx b/frontend/src/metabase/reference/databases/FieldListContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..3f207cd58fbf137ee6c7fd56c4ab0d9d9c94ab67
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/FieldListContainer.jsx
@@ -0,0 +1,76 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+import FieldList from "metabase/reference/databases/FieldList";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import {
+  getDatabase,
+  getTable,
+  getDatabaseId,
+  getIsEditing,
+} from "../selectors";
+import TableSidebar from "./TableSidebar";
+
+const mapStateToProps = (state, props) => ({
+  database: getDatabase(state, props),
+  table: getTable(state, props),
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class FieldListContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    database: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    table: PropTypes.object.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchDatabaseMetadata(
+      this.props,
+      this.props.databaseId,
+    );
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { database, table, isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<TableSidebar database={database} table={table} />}
+      >
+        <FieldList {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(FieldListContainer);
diff --git a/frontend/src/metabase/reference/databases/FieldSidebar.jsx b/frontend/src/metabase/reference/databases/FieldSidebar.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e3c3679331911826dba799c90b5f07b0393e31df
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/FieldSidebar.jsx
@@ -0,0 +1,61 @@
+/* eslint "react/prop-types": "warn" */
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { t } from "ttag";
+import cx from "classnames";
+
+import MetabaseSettings from "metabase/lib/settings";
+
+import Breadcrumbs from "metabase/components/Breadcrumbs";
+import SidebarItem from "metabase/components/SidebarItem";
+
+import S from "metabase/components/Sidebar.css";
+
+const FieldSidebar = ({ database, table, field, style, className }) => (
+  <div className={cx(S.sidebar, className)} style={style}>
+    <ul>
+      <div className={S.breadcrumbs}>
+        <Breadcrumbs
+          className="py4 ml3"
+          crumbs={[
+            [database.name, `/reference/databases/${database.id}`],
+            [
+              table.name,
+              `/reference/databases/${database.id}/tables/${table.id}`,
+            ],
+            [field.name],
+          ]}
+          inSidebar={true}
+          placeholder={t`Data Reference`}
+        />
+      </div>
+      <ol className="mx3">
+        <SidebarItem
+          key={`/reference/databases/${database.id}/tables/${table.id}/fields/${field.id}`}
+          href={`/reference/databases/${database.id}/tables/${table.id}/fields/${field.id}`}
+          icon="document"
+          name={t`Details`}
+        />
+
+        {MetabaseSettings.get("enable-xrays") && (
+          <SidebarItem
+            key={`/auto/dashboard/field/${field.id}`}
+            href={`/auto/dashboard/field/${field.id}`}
+            icon="bolt"
+            name={t`X-ray this field`}
+          />
+        )}
+      </ol>
+    </ul>
+  </div>
+);
+
+FieldSidebar.propTypes = {
+  database: PropTypes.object,
+  table: PropTypes.object,
+  field: PropTypes.object,
+  className: PropTypes.string,
+  style: PropTypes.object,
+};
+
+export default memo(FieldSidebar);
diff --git a/frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx b/frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..2f8c6d69f8e0d0aea614c09266cbfd1159cc1fab
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx
@@ -0,0 +1,17 @@
+import { t } from "ttag";
+
+import AdminAwareEmptyState from "metabase/components/AdminAwareEmptyState";
+
+const NoDatabasesEmptyState = user => (
+  <AdminAwareEmptyState
+    title={t`Metabase is no fun without any data`}
+    adminMessage={t`Your databases will appear here once you connect one`}
+    message={t`Databases will appear here once your admins have added some`}
+    image="app/assets/img/databases-list"
+    adminAction={t`Connect a database`}
+    adminLink="/admin/databases/create"
+    user={user}
+  />
+);
+
+export default NoDatabasesEmptyState;
diff --git a/frontend/src/metabase/reference/databases/TableDetail.jsx b/frontend/src/metabase/reference/databases/TableDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..41273cacf1c8e67738684573fe8c1df4ba421ac3
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/TableDetail.jsx
@@ -0,0 +1,228 @@
+/* eslint "react/prop-types": "warn" */
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { useFormik } from "formik";
+import { push } from "react-router-redux";
+import { t } from "ttag";
+import S from "metabase/reference/Reference.css";
+
+import List from "metabase/components/List";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import EditHeader from "metabase/reference/components/EditHeader";
+import EditableReferenceHeader from "metabase/reference/components/EditableReferenceHeader";
+import Detail from "metabase/reference/components/Detail";
+import UsefulQuestions from "metabase/reference/components/UsefulQuestions";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+import { getQuestionUrl } from "../utils";
+
+import {
+  getTable,
+  getFields,
+  getError,
+  getLoading,
+  getUser,
+  getIsEditing,
+  getHasSingleSchema,
+  getIsFormulaExpanded,
+  getForeignKeys,
+} from "../selectors";
+
+const interestingQuestions = table => {
+  return [
+    {
+      text: t`Count of ${table.display_name}`,
+      icon: "number",
+      link: getQuestionUrl({
+        dbId: table.db_id,
+        tableId: table.id,
+        getCount: true,
+      }),
+    },
+    {
+      text: t`See raw data for ${table.display_name}`,
+      icon: "table2",
+      link: getQuestionUrl({
+        dbId: table.db_id,
+        tableId: table.id,
+      }),
+    },
+  ];
+};
+const mapStateToProps = (state, props) => {
+  const entity = getTable(state, props) || {};
+  const fields = getFields(state, props);
+
+  return {
+    entity,
+    table: getTable(state, props),
+    metadataFields: fields,
+    loading: getLoading(state, props),
+    // naming this 'error' will conflict with redux form
+    loadingError: getError(state, props),
+    user: getUser(state, props),
+    foreignKeys: getForeignKeys(state, props),
+    isEditing: getIsEditing(state, props),
+    hasSingleSchema: getHasSingleSchema(state, props),
+    isFormulaExpanded: getIsFormulaExpanded(state, props),
+  };
+};
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+  onSubmit: actions.rUpdateTableDetail,
+  onChangeLocation: push,
+};
+
+const propTypes = {
+  style: PropTypes.object.isRequired,
+  entity: PropTypes.object.isRequired,
+  table: PropTypes.object,
+  user: PropTypes.object.isRequired,
+  isEditing: PropTypes.bool,
+  startEditing: PropTypes.func.isRequired,
+  endEditing: PropTypes.func.isRequired,
+  startLoading: PropTypes.func.isRequired,
+  endLoading: PropTypes.func.isRequired,
+  setError: PropTypes.func.isRequired,
+  updateField: PropTypes.func.isRequired,
+  handleSubmit: PropTypes.func.isRequired,
+  resetForm: PropTypes.func.isRequired,
+  fields: PropTypes.object.isRequired,
+  hasSingleSchema: PropTypes.bool,
+  loading: PropTypes.bool,
+  loadingError: PropTypes.object,
+  onSubmit: PropTypes.func.isRequired,
+};
+
+const TableDetail = props => {
+  const {
+    style,
+    entity,
+    table,
+    loadingError,
+    loading,
+    user,
+    isEditing,
+    startEditing,
+    endEditing,
+    hasSingleSchema,
+    onSubmit,
+  } = props;
+
+  const {
+    isSubmitting,
+    getFieldProps,
+    getFieldMeta,
+    handleSubmit,
+    handleReset,
+  } = useFormik({
+    initialValues: {},
+    onSubmit: fields => onSubmit(fields, { ...props, resetForm: handleReset }),
+  });
+
+  const getFormField = name => ({
+    ...getFieldProps(name),
+    ...getFieldMeta(name),
+  });
+
+  return (
+    <form style={style} className="full" onSubmit={handleSubmit}>
+      {isEditing && (
+        <EditHeader
+          hasRevisionHistory={false}
+          onSubmit={handleSubmit}
+          endEditing={endEditing}
+          reinitializeForm={handleReset}
+          submitting={isSubmitting}
+          revisionMessageFormField={getFormField("revision_message")}
+        />
+      )}
+      <EditableReferenceHeader
+        entity={entity}
+        table={table}
+        type="table"
+        headerIcon="table2"
+        headerLink={getQuestionUrl({
+          dbId: entity.db_id,
+          tableId: entity.id,
+        })}
+        name={t`Details`}
+        user={user}
+        isEditing={isEditing}
+        hasSingleSchema={hasSingleSchema}
+        hasDisplayName={true}
+        startEditing={startEditing}
+        displayNameFormField={getFormField("display_name")}
+        nameFormField={getFormField("name")}
+      />
+      <LoadingAndErrorWrapper
+        loading={!loadingError && loading}
+        error={loadingError}
+      >
+        {() => (
+          <div className="wrapper">
+            <div className="pl4 pr3 pt4 mb4 mb1 bg-white rounded bordered">
+              <List>
+                <li className="relative">
+                  <Detail
+                    id="description"
+                    name={t`Description`}
+                    description={entity.description}
+                    placeholder={t`No description yet`}
+                    isEditing={isEditing}
+                    field={getFormField("description")}
+                  />
+                </li>
+                {!isEditing && (
+                  <li className="relative">
+                    <Detail
+                      id="name"
+                      name={t`Actual name in database`}
+                      description={entity.name}
+                      subtitleClass={S.tableActualName}
+                    />
+                  </li>
+                )}
+                <li className="relative">
+                  <Detail
+                    id="points_of_interest"
+                    name={t`Why this table is interesting`}
+                    description={entity.points_of_interest}
+                    placeholder={t`Nothing interesting yet`}
+                    isEditing={isEditing}
+                    field={getFormField("points_of_interest")}
+                  />
+                </li>
+                <li className="relative">
+                  <Detail
+                    id="caveats"
+                    name={t`Things to be aware of about this table`}
+                    description={entity.caveats}
+                    placeholder={t`Nothing to be aware of yet`}
+                    isEditing={isEditing}
+                    field={getFormField("caveats")}
+                  />
+                </li>
+                {!isEditing && (
+                  <li className="relative">
+                    <UsefulQuestions
+                      questions={interestingQuestions(props.table)}
+                    />
+                  </li>
+                )}
+              </List>
+            </div>
+          </div>
+        )}
+      </LoadingAndErrorWrapper>
+    </form>
+  );
+};
+
+TableDetail.propTypes = propTypes;
+
+export default connect(mapStateToProps, mapDispatchToProps)(TableDetail);
diff --git a/frontend/src/metabase/reference/databases/TableDetailContainer.jsx b/frontend/src/metabase/reference/databases/TableDetailContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..1b0ff16dc0c9079ee0c247051a8a6afe23fa7982
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/TableDetailContainer.jsx
@@ -0,0 +1,79 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+import TableDetail from "metabase/reference/databases/TableDetail";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import {
+  getDatabase,
+  getTable,
+  getDatabaseId,
+  getIsEditing,
+} from "../selectors";
+import TableSidebar from "./TableSidebar";
+
+const mapStateToProps = (state, props) => ({
+  database: getDatabase(state, props),
+  table: getTable(state, props),
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class TableDetailContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    database: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    table: PropTypes.object.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchDatabaseMetadata(
+      this.props,
+      this.props.databaseId,
+    );
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { database, table, isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<TableSidebar database={database} table={table} />}
+      >
+        <TableDetail {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(TableDetailContainer);
diff --git a/frontend/src/metabase/reference/databases/TableList.jsx b/frontend/src/metabase/reference/databases/TableList.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..5f2d57b440f4671891ca12e08a2c338e418297d3
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/TableList.jsx
@@ -0,0 +1,146 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { t } from "ttag";
+import _ from "underscore";
+
+import S from "metabase/components/List/List.css";
+import R from "metabase/reference/Reference.css";
+
+import List from "metabase/components/List";
+import ListItem from "metabase/components/ListItem";
+import EmptyState from "metabase/components/EmptyState";
+
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import * as metadataActions from "metabase/redux/metadata";
+import ReferenceHeader from "../components/ReferenceHeader";
+
+import {
+  getDatabase,
+  getTablesByDatabase,
+  getHasSingleSchema,
+  getError,
+  getLoading,
+} from "../selectors";
+
+const emptyStateData = {
+  message: t`Tables in this database will appear here as they're added`,
+  icon: "table2",
+};
+
+const mapStateToProps = (state, props) => ({
+  database: getDatabase(state, props),
+  entities: getTablesByDatabase(state, props),
+  hasSingleSchema: getHasSingleSchema(state, props),
+  loading: getLoading(state, props),
+  loadingError: getError(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+};
+
+const createListItem = table => (
+  <ListItem
+    key={table.id}
+    name={table.display_name || table.name}
+    description={table.description}
+    url={`/reference/databases/${table.db_id}/tables/${table.id}`}
+    icon="table2"
+  />
+);
+
+const createSchemaSeparator = table => (
+  <li className={R.schemaSeparator}>{table.schema_name}</li>
+);
+
+export const separateTablesBySchema = (
+  tables,
+  createSchemaSeparator,
+  createListItem,
+) => {
+  const sortedTables = _.chain(tables)
+    .sortBy(t => t.name)
+    .sortBy(t => t.schema_name)
+    .value();
+
+  return sortedTables.map((table, index, sortedTables) => {
+    if (!table || !table.id || !table.name) {
+      return;
+    }
+    // add schema header for first element and if schema is different from previous
+    const previousTableId = Object.keys(sortedTables)[index - 1];
+    return index === 0 ||
+      sortedTables[previousTableId].schema_name !== table.schema_name
+      ? [createSchemaSeparator(table), createListItem(table)]
+      : createListItem(table);
+  });
+};
+
+class TableList extends Component {
+  static propTypes = {
+    style: PropTypes.object.isRequired,
+    entities: PropTypes.object.isRequired,
+    database: PropTypes.object.isRequired,
+    hasSingleSchema: PropTypes.bool,
+    loading: PropTypes.bool,
+    loadingError: PropTypes.object,
+  };
+
+  render() {
+    const {
+      entities,
+      style,
+      database,
+      hasSingleSchema,
+      loadingError,
+      loading,
+    } = this.props;
+
+    const tables = Object.values(entities);
+
+    return (
+      <div style={style} className="full" data-testid="table-list">
+        <ReferenceHeader
+          name={t`Tables in ${database.name}`}
+          type="tables"
+          headerIcon="database"
+        />
+        <LoadingAndErrorWrapper
+          loading={!loadingError && loading}
+          error={loadingError}
+        >
+          {() =>
+            tables.length > 0 ? (
+              <div className="wrapper wrapper--trim">
+                <List>
+                  {!hasSingleSchema
+                    ? separateTablesBySchema(
+                        tables,
+                        createSchemaSeparator,
+                        createListItem,
+                      )
+                    : _.sortBy(tables, "name").map(
+                        table =>
+                          table &&
+                          table.id &&
+                          table.name &&
+                          createListItem(table),
+                      )}
+                </List>
+              </div>
+            ) : (
+              <div className={S.empty}>
+                <EmptyState {...emptyStateData} />
+              </div>
+            )
+          }
+        </LoadingAndErrorWrapper>
+      </div>
+    );
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(TableList);
diff --git a/frontend/src/metabase/reference/databases/TableListContainer.jsx b/frontend/src/metabase/reference/databases/TableListContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..2ff183481919d53308b33f79f2df0474006b26ee
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/TableListContainer.jsx
@@ -0,0 +1,69 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+import TableList from "metabase/reference/databases/TableList";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import { getDatabase, getDatabaseId, getIsEditing } from "../selectors";
+import DatabaseSidebar from "./DatabaseSidebar";
+
+const mapStateToProps = (state, props) => ({
+  database: getDatabase(state, props),
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class TableListContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    database: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchDatabaseMetadata(
+      this.props,
+      this.props.databaseId,
+    );
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { database, isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<DatabaseSidebar database={database} />}
+      >
+        <TableList {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(TableListContainer);
diff --git a/frontend/src/metabase/reference/databases/TableQuestions.jsx b/frontend/src/metabase/reference/databases/TableQuestions.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..1af9b97c85ca16093a9024910e1b1ef6a9980436
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/TableQuestions.jsx
@@ -0,0 +1,110 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import moment from "moment-timezone";
+import { t } from "ttag";
+import visualizations from "metabase/visualizations";
+import * as Urls from "metabase/lib/urls";
+
+import S from "metabase/components/List/List.css";
+
+import List from "metabase/components/List";
+import ListItem from "metabase/components/ListItem";
+import AdminAwareEmptyState from "metabase/components/AdminAwareEmptyState";
+
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import * as metadataActions from "metabase/redux/metadata";
+import ReferenceHeader from "../components/ReferenceHeader";
+
+import { getQuestionUrl } from "../utils";
+
+import {
+  getTableQuestions,
+  getError,
+  getLoading,
+  getTable,
+} from "../selectors";
+
+const emptyStateData = table => {
+  return {
+    message: t`Questions about this table will appear here as they're added`,
+    icon: "folder",
+    action: t`Ask a question`,
+    link: getQuestionUrl({
+      dbId: table.db_id,
+      tableId: table.id,
+    }),
+  };
+};
+
+const mapStateToProps = (state, props) => ({
+  table: getTable(state, props),
+  entities: getTableQuestions(state, props),
+  loading: getLoading(state, props),
+  loadingError: getError(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+};
+
+class TableQuestions extends Component {
+  static propTypes = {
+    table: PropTypes.object.isRequired,
+    style: PropTypes.object.isRequired,
+    entities: PropTypes.object.isRequired,
+    loading: PropTypes.bool,
+    loadingError: PropTypes.object,
+  };
+
+  render() {
+    const { entities, style, loadingError, loading } = this.props;
+
+    return (
+      <div style={style} className="full">
+        <ReferenceHeader
+          name={t`Questions about ${this.props.table.display_name}`}
+          type="questions"
+          headerIcon="table2"
+        />
+        <LoadingAndErrorWrapper
+          loading={!loadingError && loading}
+          error={loadingError}
+        >
+          {() =>
+            Object.keys(entities).length > 0 ? (
+              <div className="wrapper wrapper--trim">
+                <List>
+                  {Object.values(entities).map(
+                    entity =>
+                      entity &&
+                      entity.id &&
+                      entity.name && (
+                        <ListItem
+                          key={entity.id}
+                          name={entity.display_name || entity.name}
+                          description={t`Created ${moment(
+                            entity.created_at,
+                          ).fromNow()} by ${entity.creator.common_name}`}
+                          url={Urls.question(entity)}
+                          icon={visualizations.get(entity.display).iconName}
+                        />
+                      ),
+                  )}
+                </List>
+              </div>
+            ) : (
+              <div className={S.empty}>
+                <AdminAwareEmptyState {...emptyStateData(this.props.table)} />
+              </div>
+            )
+          }
+        </LoadingAndErrorWrapper>
+      </div>
+    );
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(TableQuestions);
diff --git a/frontend/src/metabase/reference/databases/TableQuestionsContainer.jsx b/frontend/src/metabase/reference/databases/TableQuestionsContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..9d529f0a71106b23bab7916e5680664f4443f1b9
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/TableQuestionsContainer.jsx
@@ -0,0 +1,82 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+
+import TableQuestions from "metabase/reference/databases/TableQuestions";
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import Questions from "metabase/entities/questions";
+import {
+  getDatabase,
+  getTable,
+  getDatabaseId,
+  getIsEditing,
+} from "../selectors";
+
+import TableSidebar from "./TableSidebar";
+
+const mapStateToProps = (state, props) => ({
+  database: getDatabase(state, props),
+  table: getTable(state, props),
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  fetchQuestions: Questions.actions.fetchList,
+  ...metadataActions,
+  ...actions,
+};
+
+class TableQuestionsContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    database: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    table: PropTypes.object.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchDatabaseMetadataAndQuestion(
+      this.props,
+      this.props.databaseId,
+    );
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { database, table, isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<TableSidebar database={database} table={table} />}
+      >
+        <TableQuestions {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(TableQuestionsContainer);
diff --git a/frontend/src/metabase/reference/databases/TableSidebar.jsx b/frontend/src/metabase/reference/databases/TableSidebar.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..616bb4a0a86dd598eea5aea1858d2eebc000a857
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/TableSidebar.jsx
@@ -0,0 +1,66 @@
+/* eslint "react/prop-types": "warn" */
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { t } from "ttag";
+import cx from "classnames";
+
+import MetabaseSettings from "metabase/lib/settings";
+
+import Breadcrumbs from "metabase/components/Breadcrumbs";
+import SidebarItem from "metabase/components/SidebarItem";
+
+import S from "metabase/components/Sidebar.css";
+
+const TableSidebar = ({ database, table, style, className }) => (
+  <div className={cx(S.sidebar, className)} style={style}>
+    <div className={S.breadcrumbs}>
+      <Breadcrumbs
+        className="py4 ml3"
+        crumbs={[
+          [t`Databases`, "/reference/databases"],
+          [database.name, `/reference/databases/${database.id}`],
+          [table.name],
+        ]}
+        inSidebar={true}
+        placeholder={t`Data Reference`}
+      />
+    </div>
+    <ol className="mx3">
+      <SidebarItem
+        key={`/reference/databases/${database.id}/tables/${table.id}`}
+        href={`/reference/databases/${database.id}/tables/${table.id}`}
+        icon="document"
+        name={t`Details`}
+      />
+      <SidebarItem
+        key={`/reference/databases/${database.id}/tables/${table.id}/fields`}
+        href={`/reference/databases/${database.id}/tables/${table.id}/fields`}
+        icon="field"
+        name={t`Fields in this table`}
+      />
+      <SidebarItem
+        key={`/reference/databases/${database.id}/tables/${table.id}/questions`}
+        href={`/reference/databases/${database.id}/tables/${table.id}/questions`}
+        icon="folder"
+        name={t`Questions about this table`}
+      />
+      {MetabaseSettings.get("enable-xrays") && (
+        <SidebarItem
+          key={`/auto/dashboard/table/${table.id}`}
+          href={`/auto/dashboard/table/${table.id}`}
+          icon="bolt"
+          name={t`X-ray this table`}
+        />
+      )}
+    </ol>
+  </div>
+);
+
+TableSidebar.propTypes = {
+  database: PropTypes.object,
+  table: PropTypes.object,
+  className: PropTypes.string,
+  style: PropTypes.object,
+};
+
+export default memo(TableSidebar);
diff --git a/frontend/src/metabase/reference/guide/BaseSidebar.jsx b/frontend/src/metabase/reference/guide/BaseSidebar.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..324fbd10cf8ed8531f2e33ecfe6d61072fdef04e
--- /dev/null
+++ b/frontend/src/metabase/reference/guide/BaseSidebar.jsx
@@ -0,0 +1,48 @@
+/* eslint "react/prop-types": "warn" */
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { t } from "ttag";
+import cx from "classnames";
+import S from "metabase/components/Sidebar.css";
+import Breadcrumbs from "metabase/components/Breadcrumbs";
+import SidebarItem from "metabase/components/SidebarItem";
+
+const BaseSidebar = ({ style, className }) => (
+  <div className={cx(S.sidebar, className)} style={style}>
+    <div className={S.breadcrumbs}>
+      <Breadcrumbs
+        className="py4 ml3"
+        crumbs={[[t`Data Reference`]]}
+        inSidebar={true}
+        placeholder={t`Data Reference`}
+      />
+    </div>
+    <ol className="mx3">
+      <SidebarItem
+        key="/reference/metrics"
+        href="/reference/metrics"
+        icon="ruler"
+        name={t`Metrics`}
+      />
+      <SidebarItem
+        key="/reference/segments"
+        href="/reference/segments"
+        icon="segment"
+        name={t`Segments`}
+      />
+      <SidebarItem
+        key="/reference/databases"
+        href="/reference/databases"
+        icon="database"
+        name={t`Our data`}
+      />
+    </ol>
+  </div>
+);
+
+BaseSidebar.propTypes = {
+  className: PropTypes.string,
+  style: PropTypes.object,
+};
+
+export default memo(BaseSidebar);
diff --git a/frontend/src/metabase/reference/metrics/MetricDetail.jsx b/frontend/src/metabase/reference/metrics/MetricDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..83b47ef8f41c96f199794358551947cc70fbfa80
--- /dev/null
+++ b/frontend/src/metabase/reference/metrics/MetricDetail.jsx
@@ -0,0 +1,239 @@
+/* eslint "react/prop-types": "warn" */
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { useFormik } from "formik";
+import { push } from "react-router-redux";
+import { t } from "ttag";
+import _ from "underscore";
+
+import List from "metabase/components/List";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import EditHeader from "metabase/reference/components/EditHeader";
+import EditableReferenceHeader from "metabase/reference/components/EditableReferenceHeader";
+import Detail from "metabase/reference/components/Detail";
+import FieldsToGroupBy from "metabase/reference/components/FieldsToGroupBy";
+import Formula from "metabase/reference/components/Formula";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+import { getQuestionUrl } from "../utils";
+
+import {
+  getMetric,
+  getTable,
+  getFields,
+  getError,
+  getLoading,
+  getUser,
+  getIsFormulaExpanded,
+  getForeignKeys,
+} from "../selectors";
+
+const mapStateToProps = (state, props) => {
+  const entity = getMetric(state, props) || {};
+  const fields = getFields(state, props);
+
+  return {
+    entity,
+    table: getTable(state, props),
+    metadataFields: fields,
+    loading: getLoading(state, props),
+    // naming this 'error' will conflict with redux form
+    loadingError: getError(state, props),
+    user: getUser(state, props),
+    foreignKeys: getForeignKeys(state, props),
+    isFormulaExpanded: getIsFormulaExpanded(state, props),
+  };
+};
+
+const mapDispatchToProps = {
+  ...metadataActions,
+
+  // Metric page doesn't use Redux isEditing state and related callbacks
+  // The state and callbacks are received via props
+  ..._.omit(actions, "startEditing", "endEditing"),
+
+  onSubmit: actions.rUpdateMetricDetail,
+  onChangeLocation: push,
+};
+
+const validate = values =>
+  !values.revision_message
+    ? { revision_message: t`Please enter a revision message` }
+    : {};
+
+const propTypes = {
+  style: PropTypes.object.isRequired,
+  entity: PropTypes.object.isRequired,
+  table: PropTypes.object,
+  metadataFields: PropTypes.object,
+  user: PropTypes.object.isRequired,
+  isEditing: PropTypes.bool,
+  startEditing: PropTypes.func.isRequired,
+  endEditing: PropTypes.func.isRequired,
+  startLoading: PropTypes.func.isRequired,
+  endLoading: PropTypes.func.isRequired,
+  expandFormula: PropTypes.func.isRequired,
+  collapseFormula: PropTypes.func.isRequired,
+  setError: PropTypes.func.isRequired,
+  updateField: PropTypes.func.isRequired,
+  isFormulaExpanded: PropTypes.bool,
+  loading: PropTypes.bool,
+  loadingError: PropTypes.object,
+  onSubmit: PropTypes.func.isRequired,
+  onChangeLocation: PropTypes.func.isRequired,
+};
+
+const MetricDetail = props => {
+  const {
+    style,
+    entity,
+    table,
+    metadataFields,
+    loadingError,
+    loading,
+    user,
+    isEditing,
+    startEditing,
+    endEditing,
+    expandFormula,
+    collapseFormula,
+    isFormulaExpanded,
+    onSubmit,
+    onChangeLocation,
+  } = props;
+
+  const {
+    isSubmitting,
+    getFieldProps,
+    getFieldMeta,
+    handleSubmit,
+    handleReset,
+  } = useFormik({
+    validate,
+    initialValues: {},
+    initialErrors: validate({}),
+    onSubmit: fields =>
+      onSubmit(entity, fields, { ...props, resetForm: handleReset }),
+  });
+
+  const getFormField = name => ({
+    ...getFieldProps(name),
+    ...getFieldMeta(name),
+  });
+
+  return (
+    <form style={style} className="full" onSubmit={handleSubmit}>
+      {isEditing && (
+        <EditHeader
+          hasRevisionHistory={true}
+          onSubmit={handleSubmit}
+          endEditing={endEditing}
+          reinitializeForm={handleReset}
+          submitting={isSubmitting}
+          revisionMessageFormField={getFormField("revision_message")}
+        />
+      )}
+      <EditableReferenceHeader
+        entity={entity}
+        table={table}
+        type="metric"
+        headerIcon="ruler"
+        headerLink={getQuestionUrl({
+          dbId: table && table.db_id,
+          tableId: entity.table_id,
+          metricId: entity.id,
+        })}
+        name={t`Details`}
+        user={user}
+        isEditing={isEditing}
+        hasSingleSchema={false}
+        hasDisplayName={false}
+        startEditing={startEditing}
+        displayNameFormField={getFormField("display_name")}
+        nameFormField={getFormField("name")}
+      />
+      <LoadingAndErrorWrapper
+        loading={!loadingError && loading}
+        error={loadingError}
+      >
+        {() => (
+          <div className="wrapper">
+            <div className="pl4 pr3 pt4 mb4 mb1 bg-white rounded bordered">
+              <List>
+                <li className="relative">
+                  <Detail
+                    field={getFormField("description")}
+                    name={t`Description`}
+                    description={entity.description}
+                    placeholder={t`No description yet`}
+                    isEditing={isEditing}
+                  />
+                </li>
+                <li className="relative">
+                  <Detail
+                    field={getFormField("points_of_interest")}
+                    name={t`Why this metric is interesting`}
+                    description={entity.points_of_interest}
+                    placeholder={t`Nothing interesting yet`}
+                    isEditing={isEditing}
+                  />
+                </li>
+                <li className="relative">
+                  <Detail
+                    field={getFormField("caveats")}
+                    name={t`Things to be aware of about this metric`}
+                    description={entity.caveats}
+                    placeholder={t`Nothing to be aware of yet`}
+                    isEditing={isEditing}
+                  />
+                </li>
+                <li className="relative">
+                  <Detail
+                    field={getFormField("how_is_this_calculated")}
+                    name={t`How this metric is calculated`}
+                    description={entity.how_is_this_calculated}
+                    placeholder={t`Nothing on how it's calculated yet`}
+                    isEditing={isEditing}
+                  />
+                </li>
+                {table && !isEditing && (
+                  <li className="relative">
+                    <Formula
+                      type="metric"
+                      entity={entity}
+                      isExpanded={isFormulaExpanded}
+                      expandFormula={expandFormula}
+                      collapseFormula={collapseFormula}
+                    />
+                  </li>
+                )}
+                {!isEditing && (
+                  <li className="relative mt4">
+                    <FieldsToGroupBy
+                      fields={table.fields
+                        .map(fieldId => metadataFields[fieldId])
+                        .reduce(
+                          (map, field) => ({ ...map, [field.id]: field }),
+                          {},
+                        )}
+                      databaseId={table && table.db_id}
+                      metric={entity}
+                      title={t`Fields you can group this metric by`}
+                      onChangeLocation={onChangeLocation}
+                    />
+                  </li>
+                )}
+              </List>
+            </div>
+          </div>
+        )}
+      </LoadingAndErrorWrapper>
+    </form>
+  );
+};
+
+MetricDetail.propTypes = propTypes;
+
+export default connect(mapStateToProps, mapDispatchToProps)(MetricDetail);
diff --git a/frontend/src/metabase/reference/metrics/MetricDetailContainer.jsx b/frontend/src/metabase/reference/metrics/MetricDetailContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..92b5bf82aeabc936bec6420a97abe292a5fdac8f
--- /dev/null
+++ b/frontend/src/metabase/reference/metrics/MetricDetailContainer.jsx
@@ -0,0 +1,100 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import * as MetabaseAnalytics from "metabase/lib/analytics";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+import MetricDetail from "metabase/reference/metrics/MetricDetail";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import { getUser, getMetric, getMetricId, getDatabaseId } from "../selectors";
+import MetricSidebar from "./MetricSidebar";
+
+const mapStateToProps = (state, props) => ({
+  user: getUser(state, props),
+  metric: getMetric(state, props),
+  metricId: getMetricId(state, props),
+  databaseId: getDatabaseId(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class MetricDetailContainer extends Component {
+  static propTypes = {
+    router: PropTypes.shape({
+      replace: PropTypes.func.isRequired,
+    }).isRequired,
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    user: PropTypes.object.isRequired,
+    metric: PropTypes.object.isRequired,
+    metricId: PropTypes.number.isRequired,
+    databaseId: PropTypes.number.isRequired,
+  };
+
+  constructor(props) {
+    super(props);
+    this.startEditing = this.startEditing.bind(this);
+    this.endEditing = this.endEditing.bind(this);
+  }
+
+  async fetchContainerData() {
+    await actions.wrappedFetchMetricDetail(this.props, this.props.metricId);
+  }
+
+  startEditing() {
+    const { metric, router } = this.props;
+    router.replace(`/reference/metrics/${metric.id}/edit`);
+    MetabaseAnalytics.trackStructEvent("Data Reference", "Started Editing");
+  }
+
+  endEditing() {
+    const { metric, router } = this.props;
+    router.replace(`/reference/metrics/${metric.id}`);
+    // No need to track end of editing here, as it's done by actions.clearState below
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { location, user, metric } = this.props;
+    const isEditing = location.pathname.endsWith("/edit");
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<MetricSidebar metric={metric} user={user} />}
+      >
+        <MetricDetail
+          {...this.props}
+          isEditing={isEditing}
+          startEditing={this.startEditing}
+          endEditing={this.endEditing}
+        />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(MetricDetailContainer);
diff --git a/frontend/src/metabase/reference/metrics/MetricList.jsx b/frontend/src/metabase/reference/metrics/MetricList.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..6097af58dd4983936e4822760c71019579eef0fa
--- /dev/null
+++ b/frontend/src/metabase/reference/metrics/MetricList.jsx
@@ -0,0 +1,93 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { t } from "ttag";
+
+import S from "metabase/components/List/List.css";
+
+import List from "metabase/components/List";
+import ListItem from "metabase/components/ListItem";
+import AdminAwareEmptyState from "metabase/components/AdminAwareEmptyState";
+
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import MetabaseSettings from "metabase/lib/settings";
+import * as metadataActions from "metabase/redux/metadata";
+import ReferenceHeader from "../components/ReferenceHeader";
+
+import { getMetrics, getError, getLoading } from "../selectors";
+
+const emptyStateData = {
+  title: t`Metrics are the official numbers that your team cares about`,
+  adminMessage: t`Defining common metrics for your team makes it even easier to ask questions`,
+  message: t`Metrics will appear here once your admins have created some`,
+  image: "app/assets/img/metrics-list",
+  adminAction: t`Learn how to create metrics`,
+  adminLink: MetabaseSettings.docsUrl(
+    "data-modeling/segments-and-metrics",
+    "creating-a-metric",
+  ),
+};
+
+const mapStateToProps = (state, props) => ({
+  entities: getMetrics(state, props),
+  loading: getLoading(state, props),
+  loadingError: getError(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+};
+
+class MetricList extends Component {
+  static propTypes = {
+    style: PropTypes.object.isRequired,
+    entities: PropTypes.object.isRequired,
+    loading: PropTypes.bool,
+    loadingError: PropTypes.object,
+  };
+
+  render() {
+    const { entities, style, loadingError, loading } = this.props;
+
+    return (
+      <div style={style} className="full">
+        <ReferenceHeader name={t`Metrics`} />
+        <LoadingAndErrorWrapper
+          loading={!loadingError && loading}
+          error={loadingError}
+        >
+          {() =>
+            Object.keys(entities).length > 0 ? (
+              <div className="wrapper wrapper--trim">
+                <List>
+                  {Object.values(entities).map(
+                    entity =>
+                      entity &&
+                      entity.id &&
+                      entity.name && (
+                        <ListItem
+                          key={entity.id}
+                          name={entity.display_name || entity.name}
+                          description={entity.description}
+                          url={`/reference/metrics/${entity.id}`}
+                          icon="ruler"
+                        />
+                      ),
+                  )}
+                </List>
+              </div>
+            ) : (
+              <div className={S.empty}>
+                <AdminAwareEmptyState {...emptyStateData} />
+              </div>
+            )
+          }
+        </LoadingAndErrorWrapper>
+      </div>
+    );
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(MetricList);
diff --git a/frontend/src/metabase/reference/metrics/MetricListContainer.jsx b/frontend/src/metabase/reference/metrics/MetricListContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..0dadc5950800d8ff0599018cadfe5e0dc7c35bdc
--- /dev/null
+++ b/frontend/src/metabase/reference/metrics/MetricListContainer.jsx
@@ -0,0 +1,67 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import BaseSidebar from "metabase/reference/guide/BaseSidebar";
+import SidebarLayout from "metabase/components/SidebarLayout";
+import MetricList from "metabase/reference/metrics/MetricList";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import { getDatabaseId, getIsEditing } from "../selectors";
+
+const mapStateToProps = (state, props) => ({
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class MetricListContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchMetrics(this.props);
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<BaseSidebar />}
+      >
+        <MetricList {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(MetricListContainer);
diff --git a/frontend/src/metabase/reference/metrics/MetricQuestions.jsx b/frontend/src/metabase/reference/metrics/MetricQuestions.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..0a764a89c4306c6a9f9fd78d5818999d631d5fe5
--- /dev/null
+++ b/frontend/src/metabase/reference/metrics/MetricQuestions.jsx
@@ -0,0 +1,116 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import moment from "moment-timezone";
+import { t } from "ttag";
+import visualizations from "metabase/visualizations";
+import * as Urls from "metabase/lib/urls";
+
+import S from "metabase/components/List/List.css";
+
+import List from "metabase/components/List";
+import ListItem from "metabase/components/ListItem";
+import AdminAwareEmptyState from "metabase/components/AdminAwareEmptyState";
+
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import * as metadataActions from "metabase/redux/metadata";
+import ReferenceHeader from "../components/ReferenceHeader";
+
+import { getQuestionUrl } from "../utils";
+
+import {
+  getMetricQuestions,
+  getError,
+  getLoading,
+  getTable,
+  getMetric,
+} from "../selectors";
+
+const emptyStateData = (table, metric) => {
+  return {
+    message: t`Questions about this metric will appear here as they're added`,
+    icon: "all",
+    action: t`Ask a question`,
+    link: getQuestionUrl({
+      dbId: table && table.db_id,
+      tableId: metric.table_id,
+      metricId: metric.id,
+    }),
+  };
+};
+
+const mapStateToProps = (state, props) => ({
+  metric: getMetric(state, props),
+  table: getTable(state, props),
+  entities: getMetricQuestions(state, props),
+  loading: getLoading(state, props),
+  loadingError: getError(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+};
+
+class MetricQuestions extends Component {
+  static propTypes = {
+    style: PropTypes.object.isRequired,
+    entities: PropTypes.object.isRequired,
+    loading: PropTypes.bool,
+    loadingError: PropTypes.object,
+    metric: PropTypes.object,
+    table: PropTypes.object,
+  };
+
+  render() {
+    const { entities, style, loadingError, loading } = this.props;
+
+    return (
+      <div style={style} className="full">
+        <ReferenceHeader
+          name={t`Questions about ${this.props.metric.name}`}
+          type="questions"
+          headerIcon="ruler"
+        />
+        <LoadingAndErrorWrapper
+          loading={!loadingError && loading}
+          error={loadingError}
+        >
+          {() =>
+            Object.keys(entities).length > 0 ? (
+              <div className="wrapper wrapper--trim">
+                <List>
+                  {Object.values(entities).map(
+                    entity =>
+                      entity &&
+                      entity.id &&
+                      entity.name && (
+                        <ListItem
+                          key={entity.id}
+                          name={entity.display_name || entity.name}
+                          description={t`Created ${moment(
+                            entity.created_at,
+                          ).fromNow()} by ${entity.creator.common_name}`}
+                          url={Urls.question(entity)}
+                          icon={visualizations.get(entity.display).iconName}
+                        />
+                      ),
+                  )}
+                </List>
+              </div>
+            ) : (
+              <div className={S.empty}>
+                <AdminAwareEmptyState
+                  {...emptyStateData(this.props.table, this.props.metric)}
+                />
+              </div>
+            )
+          }
+        </LoadingAndErrorWrapper>
+      </div>
+    );
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(MetricQuestions);
diff --git a/frontend/src/metabase/reference/metrics/MetricQuestionsContainer.jsx b/frontend/src/metabase/reference/metrics/MetricQuestionsContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..63750c61eb3532518534e5ea127b2822309a7fa4
--- /dev/null
+++ b/frontend/src/metabase/reference/metrics/MetricQuestionsContainer.jsx
@@ -0,0 +1,82 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+import MetricQuestions from "metabase/reference/metrics/MetricQuestions";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import Questions from "metabase/entities/questions";
+import {
+  getUser,
+  getMetric,
+  getMetricId,
+  getDatabaseId,
+  getIsEditing,
+} from "../selectors";
+
+import MetricSidebar from "./MetricSidebar";
+
+const mapStateToProps = (state, props) => ({
+  user: getUser(state, props),
+  metric: getMetric(state, props),
+  metricId: getMetricId(state, props),
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  fetchQuestions: Questions.actions.fetchList,
+  ...metadataActions,
+  ...actions,
+};
+
+class MetricQuestionsContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    user: PropTypes.object.isRequired,
+    metric: PropTypes.object.isRequired,
+    metricId: PropTypes.number.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchMetricQuestions(this.props, this.props.metricId);
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { user, metric, isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<MetricSidebar metric={metric} user={user} />}
+      >
+        <MetricQuestions {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(MetricQuestionsContainer);
diff --git a/frontend/src/metabase/reference/metrics/MetricRevisions.jsx b/frontend/src/metabase/reference/metrics/MetricRevisions.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..81d40960a1a5440d678544d805a6207974cbd577
--- /dev/null
+++ b/frontend/src/metabase/reference/metrics/MetricRevisions.jsx
@@ -0,0 +1,129 @@
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { t } from "ttag";
+import { getIn } from "icepick";
+
+import S from "metabase/components/List/List.css";
+import R from "metabase/reference/Reference.css";
+
+import * as metadataActions from "metabase/redux/metadata";
+import { assignUserColors } from "metabase/lib/formatting";
+
+import Revision from "metabase/admin/datamodel/components/revisions/Revision";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+import EmptyState from "metabase/components/EmptyState";
+import {
+  getMetricRevisions,
+  getMetric,
+  getSegment,
+  getTables,
+  getUser,
+  getLoading,
+  getError,
+} from "../selectors";
+import ReferenceHeader from "../components/ReferenceHeader";
+
+const emptyStateData = {
+  message: t`There are no revisions for this metric`,
+};
+
+const mapStateToProps = (state, props) => {
+  return {
+    revisions: getMetricRevisions(state, props),
+    metric: getMetric(state, props),
+    segment: getSegment(state, props),
+    tables: getTables(state, props),
+    user: getUser(state, props),
+    loading: getLoading(state, props),
+    loadingError: getError(state, props),
+  };
+};
+
+const mapDispatchToProps = {
+  ...metadataActions,
+};
+
+class MetricRevisions extends Component {
+  static propTypes = {
+    style: PropTypes.object.isRequired,
+    revisions: PropTypes.object.isRequired,
+    metric: PropTypes.object.isRequired,
+    segment: PropTypes.object.isRequired,
+    tables: PropTypes.object.isRequired,
+    user: PropTypes.object.isRequired,
+    loading: PropTypes.bool,
+    loadingError: PropTypes.object,
+  };
+
+  render() {
+    const {
+      style,
+      revisions,
+      metric,
+      segment,
+      tables,
+      user,
+      loading,
+      loadingError,
+    } = this.props;
+
+    const entity = metric.id ? metric : segment;
+
+    const userColorAssignments =
+      user && Object.keys(revisions).length > 0
+        ? assignUserColors(
+            Object.values(revisions).map(revision =>
+              getIn(revision, ["user", "id"]),
+            ),
+            user.id,
+          )
+        : {};
+
+    return (
+      <div style={style} className="full">
+        <ReferenceHeader
+          name={t`Revision history for ${this.props.metric.name}`}
+          headerIcon="ruler"
+        />
+        <LoadingAndErrorWrapper
+          loading={!loadingError && loading}
+          error={loadingError}
+        >
+          {() =>
+            Object.keys(revisions).length > 0 && tables[entity.table_id] ? (
+              <div className="wrapper wrapper--trim">
+                <div className={R.revisionsWrapper}>
+                  {Object.values(revisions)
+                    .map(revision =>
+                      revision && revision.diff ? (
+                        <Revision
+                          key={revision.id}
+                          revision={revision || {}}
+                          tableMetadata={tables[entity.table_id] || {}}
+                          objectName={entity.name}
+                          currentUser={user || {}}
+                          userColor={
+                            userColorAssignments[
+                              getIn(revision, ["user", "id"])
+                            ]
+                          }
+                        />
+                      ) : null,
+                    )
+                    .reverse()}
+                </div>
+              </div>
+            ) : (
+              <div className={S.empty}>
+                <EmptyState {...emptyStateData} />
+              </div>
+            )
+          }
+        </LoadingAndErrorWrapper>
+      </div>
+    );
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(MetricRevisions);
diff --git a/frontend/src/metabase/reference/metrics/MetricRevisionsContainer.jsx b/frontend/src/metabase/reference/metrics/MetricRevisionsContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..4445530b4356c96729d305098d0b3b0ec78171c0
--- /dev/null
+++ b/frontend/src/metabase/reference/metrics/MetricRevisionsContainer.jsx
@@ -0,0 +1,79 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+import MetricRevisions from "metabase/reference/metrics/MetricRevisions";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import {
+  getUser,
+  getMetric,
+  getMetricId,
+  getDatabaseId,
+  getIsEditing,
+} from "../selectors";
+import MetricSidebar from "./MetricSidebar";
+
+const mapStateToProps = (state, props) => ({
+  user: getUser(state, props),
+  metric: getMetric(state, props),
+  metricId: getMetricId(state, props),
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class MetricRevisionsContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    user: PropTypes.object.isRequired,
+    metric: PropTypes.object.isRequired,
+    metricId: PropTypes.number.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchMetricRevisions(this.props, this.props.metricId);
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { user, metric, isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<MetricSidebar metric={metric} user={user} />}
+      >
+        <MetricRevisions {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(MetricRevisionsContainer);
diff --git a/frontend/src/metabase/reference/metrics/MetricSidebar.jsx b/frontend/src/metabase/reference/metrics/MetricSidebar.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..abc87e2fe15923b9d55250c7e651dc5029430e05
--- /dev/null
+++ b/frontend/src/metabase/reference/metrics/MetricSidebar.jsx
@@ -0,0 +1,66 @@
+/* eslint "react/prop-types": "warn" */
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { t } from "ttag";
+import cx from "classnames";
+
+import MetabaseSettings from "metabase/lib/settings";
+
+import Breadcrumbs from "metabase/components/Breadcrumbs";
+import SidebarItem from "metabase/components/SidebarItem";
+
+import S from "metabase/components/Sidebar.css";
+
+const MetricSidebar = ({ metric, user, style, className }) => (
+  <div className={cx(S.sidebar, className)} style={style}>
+    <ul>
+      <div className={S.breadcrumbs}>
+        <Breadcrumbs
+          className="py4 ml3"
+          crumbs={[[t`Metrics`, "/reference/metrics"], [metric.name]]}
+          inSidebar={true}
+          placeholder={t`Data Reference`}
+        />
+      </div>
+      <ol className="mx3">
+        <SidebarItem
+          key={`/reference/metrics/${metric.id}`}
+          href={`/reference/metrics/${metric.id}`}
+          icon="document"
+          name={t`Details`}
+        />
+        <SidebarItem
+          key={`/reference/metrics/${metric.id}/questions`}
+          href={`/reference/metrics/${metric.id}/questions`}
+          icon="folder"
+          name={t`Questions about ${metric.name}`}
+        />
+        {MetabaseSettings.get("enable-xrays") && (
+          <SidebarItem
+            key={`/auto/dashboard/metric/${metric.id}`}
+            href={`/auto/dashboard/metric/${metric.id}`}
+            icon="bolt"
+            name={t`X-ray this metric`}
+          />
+        )}
+        {user && user.is_superuser && (
+          <SidebarItem
+            key={`/reference/metrics/${metric.id}/revisions`}
+            href={`/reference/metrics/${metric.id}/revisions`}
+            icon="history"
+            name={t`Revision history for ${metric.name}`}
+          />
+        )}
+      </ol>
+    </ul>
+  </div>
+);
+
+MetricSidebar.propTypes = {
+  metric: PropTypes.object,
+  user: PropTypes.object,
+  className: PropTypes.string,
+  style: PropTypes.object,
+};
+
+export default memo(MetricSidebar);
diff --git a/frontend/src/metabase/reference/reference.js b/frontend/src/metabase/reference/reference.js
new file mode 100644
index 0000000000000000000000000000000000000000..8988fdc22e797447ec708582348c71dd48fea892
--- /dev/null
+++ b/frontend/src/metabase/reference/reference.js
@@ -0,0 +1,278 @@
+import { assoc } from "icepick";
+
+import { handleActions, createAction } from "metabase/lib/redux";
+
+import * as MetabaseAnalytics from "metabase/lib/analytics";
+
+import { filterUntouchedFields, isEmptyObject } from "./utils.js";
+
+export const SET_ERROR = "metabase/reference/SET_ERROR";
+export const CLEAR_ERROR = "metabase/reference/CLEAR_ERROR";
+export const START_LOADING = "metabase/reference/START_LOADING";
+export const END_LOADING = "metabase/reference/END_LOADING";
+export const START_EDITING = "metabase/reference/START_EDITING";
+export const END_EDITING = "metabase/reference/END_EDITING";
+export const EXPAND_FORMULA = "metabase/reference/EXPAND_FORMULA";
+export const COLLAPSE_FORMULA = "metabase/reference/COLLAPSE_FORMULA";
+export const SHOW_DASHBOARD_MODAL = "metabase/reference/SHOW_DASHBOARD_MODAL";
+export const HIDE_DASHBOARD_MODAL = "metabase/reference/HIDE_DASHBOARD_MODAL";
+
+export const setError = createAction(SET_ERROR);
+
+export const clearError = createAction(CLEAR_ERROR);
+
+export const startLoading = createAction(START_LOADING);
+
+export const endLoading = createAction(END_LOADING);
+
+export const startEditing = createAction(START_EDITING, () => {
+  MetabaseAnalytics.trackStructEvent("Data Reference", "Started Editing");
+});
+
+export const endEditing = createAction(END_EDITING, () => {
+  MetabaseAnalytics.trackStructEvent("Data Reference", "Ended Editing");
+});
+
+export const expandFormula = createAction(EXPAND_FORMULA);
+
+export const collapseFormula = createAction(COLLAPSE_FORMULA);
+
+//TODO: consider making an app-wide modal state reducer and related actions
+export const showDashboardModal = createAction(SHOW_DASHBOARD_MODAL);
+
+export const hideDashboardModal = createAction(HIDE_DASHBOARD_MODAL);
+
+// Helper functions. This is meant to be a transitional state to get things out of tryFetchData() and friends
+
+const fetchDataWrapper = (props, fn) => {
+  return async argument => {
+    props.clearError();
+    props.startLoading();
+    try {
+      await fn(argument);
+    } catch (error) {
+      console.error(error);
+      props.setError(error);
+    }
+
+    props.endLoading();
+  };
+};
+
+export const wrappedFetchDatabaseMetadata = (props, databaseID) => {
+  fetchDataWrapper(props, props.fetchDatabaseMetadata)(databaseID);
+};
+
+export const wrappedFetchDatabaseMetadataAndQuestion = async (
+  props,
+  databaseID,
+) => {
+  fetchDataWrapper(props, async dbID => {
+    await Promise.all([
+      props.fetchDatabaseMetadata(dbID),
+      props.fetchQuestions(),
+    ]);
+  })(databaseID);
+};
+export const wrappedFetchMetricDetail = async (props, metricID) => {
+  fetchDataWrapper(props, async mID => {
+    await Promise.all([props.fetchMetricTable(mID), props.fetchMetrics()]);
+  })(metricID);
+};
+export const wrappedFetchMetricQuestions = async (props, metricID) => {
+  fetchDataWrapper(props, async mID => {
+    await Promise.all([
+      props.fetchMetricTable(mID),
+      props.fetchMetrics(),
+      props.fetchQuestions(),
+    ]);
+  })(metricID);
+};
+export const wrappedFetchMetricRevisions = async (props, metricID) => {
+  fetchDataWrapper(props, async mID => {
+    await Promise.all([props.fetchMetricRevisions(mID), props.fetchMetrics()]);
+  })(metricID);
+};
+
+export const wrappedFetchDatabases = props => {
+  fetchDataWrapper(props, props.fetchRealDatabases)({});
+};
+export const wrappedFetchMetrics = props => {
+  fetchDataWrapper(props, props.fetchMetrics)({});
+};
+
+export const wrappedFetchSegments = props => {
+  fetchDataWrapper(props, props.fetchSegments)({});
+};
+
+export const wrappedFetchSegmentDetail = (props, segmentID) => {
+  fetchDataWrapper(props, props.fetchSegmentTable)(segmentID);
+};
+
+export const wrappedFetchSegmentQuestions = async (props, segmentID) => {
+  fetchDataWrapper(props, async sID => {
+    await props.fetchSegments(sID);
+    await Promise.all([props.fetchSegmentTable(sID), props.fetchQuestions()]);
+  })(segmentID);
+};
+export const wrappedFetchSegmentRevisions = async (props, segmentID) => {
+  fetchDataWrapper(props, async sID => {
+    await props.fetchSegments(sID);
+    await Promise.all([
+      props.fetchSegmentRevisions(sID),
+      props.fetchSegmentTable(sID),
+    ]);
+  })(segmentID);
+};
+export const wrappedFetchSegmentFields = async (props, segmentID) => {
+  fetchDataWrapper(props, async sID => {
+    await props.fetchSegments(sID);
+    await Promise.all([
+      props.fetchSegmentFields(sID),
+      props.fetchSegmentTable(sID),
+    ]);
+  })(segmentID);
+};
+
+// This is called when a component gets a new set of props.
+// I *think* this is un-necessary in all cases as we're using multiple
+// components where the old code re-used the same component
+export const clearState = props => {
+  props.endEditing();
+  props.endLoading();
+  props.clearError();
+  props.collapseFormula();
+};
+
+// This is called on the success or failure of a form triggered update
+const resetForm = props => {
+  props.resetForm();
+  props.endLoading();
+  props.endEditing();
+};
+
+// Update actions
+// these use the "fetchDataWrapper" for now. It should probably be renamed.
+// Using props to fire off actions, which imo should be refactored to
+// dispatch directly, since there is no actual dependence with the props
+// of that component
+
+const updateDataWrapper = (props, fn) => {
+  return async fields => {
+    props.clearError();
+    props.startLoading();
+    try {
+      const editedFields = filterUntouchedFields(fields, props.entity);
+      if (!isEmptyObject(editedFields)) {
+        const newEntity = { ...props.entity, ...editedFields };
+        await fn(newEntity);
+      }
+    } catch (error) {
+      console.error(error);
+      props.setError(error);
+    }
+    resetForm(props);
+  };
+};
+
+export const rUpdateSegmentDetail = (formFields, props) => {
+  return () => updateDataWrapper(props, props.updateSegment)(formFields);
+};
+export const rUpdateSegmentFieldDetail = (formFields, props) => {
+  return () => updateDataWrapper(props, props.updateField)(formFields);
+};
+export const rUpdateDatabaseDetail = (formFields, props) => {
+  return () => updateDataWrapper(props, props.updateDatabase)(formFields);
+};
+export const rUpdateTableDetail = (formFields, props) => {
+  return () => updateDataWrapper(props, props.updateTable)(formFields);
+};
+export const rUpdateFieldDetail = (formFields, props) => {
+  return () => updateDataWrapper(props, props.updateField)(formFields);
+};
+
+export const rUpdateMetricDetail = (metric, formFields, props) => {
+  return async () => {
+    props.startLoading();
+    try {
+      const editedFields = filterUntouchedFields(formFields, metric);
+      if (!isEmptyObject(editedFields)) {
+        const newMetric = { ...metric, ...editedFields };
+        await props.updateMetric(newMetric);
+      }
+    } catch (error) {
+      props.setError(error);
+      console.error(error);
+    }
+
+    resetForm(props);
+  };
+};
+
+export const rUpdateFields = (fields, formFields, props) => {
+  return async () => {
+    props.startLoading();
+    try {
+      const updatedFields = Object.keys(formFields)
+        .map(fieldId => ({
+          field: fields[fieldId],
+          formField: filterUntouchedFields(
+            formFields[fieldId],
+            fields[fieldId],
+          ),
+        }))
+        .filter(({ field, formField }) => !isEmptyObject(formField))
+        .map(({ field, formField }) => ({ ...field, ...formField }));
+
+      await Promise.all(updatedFields.map(props.updateField));
+    } catch (error) {
+      props.setError(error);
+      console.error(error);
+    }
+
+    resetForm(props);
+  };
+};
+
+const initialState = {
+  error: null,
+  isLoading: false,
+  isEditing: false,
+  isFormulaExpanded: false,
+  isDashboardModalOpen: false,
+};
+export default handleActions(
+  {
+    [SET_ERROR]: {
+      throw: (state, { payload }) => assoc(state, "error", payload),
+    },
+    [CLEAR_ERROR]: {
+      next: state => assoc(state, "error", null),
+    },
+    [START_LOADING]: {
+      next: state => assoc(state, "isLoading", true),
+    },
+    [END_LOADING]: {
+      next: state => assoc(state, "isLoading", false),
+    },
+    [START_EDITING]: {
+      next: state => assoc(state, "isEditing", true),
+    },
+    [END_EDITING]: {
+      next: state => assoc(state, "isEditing", false),
+    },
+    [EXPAND_FORMULA]: {
+      next: state => assoc(state, "isFormulaExpanded", true),
+    },
+    [COLLAPSE_FORMULA]: {
+      next: state => assoc(state, "isFormulaExpanded", false),
+    },
+    [SHOW_DASHBOARD_MODAL]: {
+      next: state => assoc(state, "isDashboardModalOpen", true),
+    },
+    [HIDE_DASHBOARD_MODAL]: {
+      next: state => assoc(state, "isDashboardModalOpen", false),
+    },
+  },
+  initialState,
+);
diff --git a/frontend/src/metabase/reference/segments/SegmentDetail.jsx b/frontend/src/metabase/reference/segments/SegmentDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..50566a86a62db0dc4d193ec4578ec7c39067e685
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentDetail.jsx
@@ -0,0 +1,263 @@
+/* eslint "react/prop-types": "warn" */
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { useFormik } from "formik";
+import { t } from "ttag";
+import List from "metabase/components/List";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import EditHeader from "metabase/reference/components/EditHeader";
+import EditableReferenceHeader from "metabase/reference/components/EditableReferenceHeader";
+import Detail from "metabase/reference/components/Detail";
+import UsefulQuestions from "metabase/reference/components/UsefulQuestions";
+import Formula from "metabase/reference/components/Formula";
+import Link from "metabase/core/components/Link";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+import { getQuestionUrl } from "../utils";
+
+import {
+  getSegment,
+  getTable,
+  getFields,
+  getError,
+  getLoading,
+  getUser,
+  getIsEditing,
+  getIsFormulaExpanded,
+} from "../selectors";
+
+import S from "../components/Detail.css";
+
+const interestingQuestions = (table, segment) => {
+  return [
+    {
+      text: t`Number of ${segment.name}`,
+      icon: "number",
+      link: getQuestionUrl({
+        dbId: table && table.db_id,
+        tableId: table.id,
+        segmentId: segment.id,
+        getCount: true,
+      }),
+    },
+    {
+      text: t`See all ${segment.name}`,
+      icon: "table2",
+      link: getQuestionUrl({
+        dbId: table && table.db_id,
+        tableId: table.id,
+        segmentId: segment.id,
+      }),
+    },
+  ];
+};
+
+const mapStateToProps = (state, props) => {
+  const entity = getSegment(state, props) || {};
+  const fields = getFields(state, props);
+
+  return {
+    entity,
+    table: getTable(state, props),
+    metadataFields: fields,
+    loading: getLoading(state, props),
+    // naming this 'error' will conflict with redux form
+    loadingError: getError(state, props),
+    user: getUser(state, props),
+    isEditing: getIsEditing(state, props),
+    isFormulaExpanded: getIsFormulaExpanded(state, props),
+  };
+};
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+  onSubmit: actions.rUpdateSegmentDetail,
+};
+
+const validate = values =>
+  !values.revision_message
+    ? { revision_message: t`Please enter a revision message` }
+    : {};
+
+const propTypes = {
+  style: PropTypes.object.isRequired,
+  entity: PropTypes.object.isRequired,
+  table: PropTypes.object,
+  user: PropTypes.object.isRequired,
+  isEditing: PropTypes.bool,
+  startEditing: PropTypes.func.isRequired,
+  endEditing: PropTypes.func.isRequired,
+  startLoading: PropTypes.func.isRequired,
+  endLoading: PropTypes.func.isRequired,
+  expandFormula: PropTypes.func.isRequired,
+  collapseFormula: PropTypes.func.isRequired,
+  setError: PropTypes.func.isRequired,
+  updateField: PropTypes.func.isRequired,
+  isFormulaExpanded: PropTypes.bool,
+  loading: PropTypes.bool,
+  loadingError: PropTypes.object,
+  onSubmit: PropTypes.func.isRequired,
+};
+
+const SegmentDetail = props => {
+  const {
+    style,
+    entity,
+    table,
+    loadingError,
+    loading,
+    user,
+    isEditing,
+    startEditing,
+    endEditing,
+    expandFormula,
+    collapseFormula,
+    isFormulaExpanded,
+    onSubmit,
+  } = props;
+
+  const {
+    isSubmitting,
+    getFieldProps,
+    getFieldMeta,
+    handleSubmit,
+    handleReset,
+  } = useFormik({
+    validate,
+    initialValues: {},
+    initialErrors: validate({}),
+    onSubmit: fields => onSubmit(fields, { ...props, resetForm: handleReset }),
+  });
+
+  const getFormField = name => ({
+    ...getFieldProps(name),
+    ...getFieldMeta(name),
+  });
+
+  return (
+    <form style={style} className="full" onSubmit={handleSubmit}>
+      {isEditing && (
+        <EditHeader
+          hasRevisionHistory={true}
+          onSubmit={handleSubmit}
+          endEditing={endEditing}
+          reinitializeForm={handleReset}
+          submitting={isSubmitting}
+          revisionMessageFormField={getFormField("revision_message")}
+        />
+      )}
+      <EditableReferenceHeader
+        entity={entity}
+        table={table}
+        type="segment"
+        headerIcon="segment"
+        headerLink={getQuestionUrl({
+          dbId: table && table.db_id,
+          tableId: entity.table_id,
+          segmentId: entity.id,
+        })}
+        name={t`Details`}
+        user={user}
+        isEditing={isEditing}
+        hasSingleSchema={false}
+        hasDisplayName={false}
+        startEditing={startEditing}
+        displayNameFormField={getFormField("display_name")}
+        nameFormField={getFormField("name")}
+      />
+      <LoadingAndErrorWrapper
+        loading={!loadingError && loading}
+        error={loadingError}
+      >
+        {() => (
+          <div className="wrapper">
+            <div className="pl4 pr3 pt4 mb4 mb1 bg-white rounded bordered">
+              <List>
+                <li>
+                  <div className={S.detail}>
+                    <div className={S.detailBody}>
+                      <div>
+                        <div className={S.detailTitle}>
+                          {t`Table this is based on`}
+                        </div>
+                        {table && (
+                          <div>
+                            <Link
+                              className="text-brand text-bold text-paragraph"
+                              to={`/reference/databases/${table.db_id}/tables/${table.id}`}
+                            >
+                              <span className="pt1">{table.display_name}</span>
+                            </Link>
+                          </div>
+                        )}
+                      </div>
+                    </div>
+                  </div>
+                </li>
+                <li className="relative">
+                  <Detail
+                    id="description"
+                    name={t`Description`}
+                    description={entity.description}
+                    placeholder={t`No description yet`}
+                    isEditing={isEditing}
+                    field={getFormField("description")}
+                  />
+                </li>
+                <li className="relative">
+                  <Detail
+                    id="points_of_interest"
+                    name={t`Why this Segment is interesting`}
+                    description={entity.points_of_interest}
+                    placeholder={t`Nothing interesting yet`}
+                    isEditing={isEditing}
+                    field={getFormField("points_of_interest")}
+                  />
+                </li>
+                <li className="relative">
+                  <Detail
+                    id="caveats"
+                    name={t`Things to be aware of about this Segment`}
+                    description={entity.caveats}
+                    placeholder={t`Nothing to be aware of yet`}
+                    isEditing={isEditing}
+                    field={getFormField("caveats")}
+                  />
+                </li>
+                {!isEditing && (
+                  <li className="relative">
+                    <UsefulQuestions
+                      questions={interestingQuestions(
+                        props.table,
+                        props.entity,
+                      )}
+                    />
+                  </li>
+                )}
+                {table && !isEditing && (
+                  <li className="relative mb4">
+                    <Formula
+                      type="segment"
+                      entity={entity}
+                      table={table}
+                      isExpanded={isFormulaExpanded}
+                      expandFormula={expandFormula}
+                      collapseFormula={collapseFormula}
+                    />
+                  </li>
+                )}
+              </List>
+            </div>
+          </div>
+        )}
+      </LoadingAndErrorWrapper>
+    </form>
+  );
+};
+
+SegmentDetail.propTypes = propTypes;
+
+export default connect(mapStateToProps, mapDispatchToProps)(SegmentDetail);
diff --git a/frontend/src/metabase/reference/segments/SegmentDetailContainer.jsx b/frontend/src/metabase/reference/segments/SegmentDetailContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..8e0a2959417f3d75b7351c2ab2535694ac83954e
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentDetailContainer.jsx
@@ -0,0 +1,79 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+import SegmentDetail from "metabase/reference/segments/SegmentDetail";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import {
+  getUser,
+  getSegment,
+  getSegmentId,
+  getDatabaseId,
+  getIsEditing,
+} from "../selectors";
+import SegmentSidebar from "./SegmentSidebar";
+
+const mapStateToProps = (state, props) => ({
+  user: getUser(state, props),
+  segment: getSegment(state, props),
+  segmentId: getSegmentId(state, props),
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class SegmentDetailContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    user: PropTypes.object.isRequired,
+    segment: PropTypes.object.isRequired,
+    segmentId: PropTypes.number.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchSegmentDetail(this.props, this.props.segmentId);
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { user, segment, isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<SegmentSidebar segment={segment} user={user} />}
+      >
+        <SegmentDetail {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(SegmentDetailContainer);
diff --git a/frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx b/frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a774c42bfb4bd8343e172fafab77d05fe831ba9f
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx
@@ -0,0 +1,243 @@
+/* eslint "react/prop-types": "warn" */
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { useFormik } from "formik";
+import { t } from "ttag";
+import S from "metabase/reference/Reference.css";
+
+import List from "metabase/components/List";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import EditHeader from "metabase/reference/components/EditHeader";
+import EditableReferenceHeader from "metabase/reference/components/EditableReferenceHeader";
+import Detail from "metabase/reference/components/Detail";
+import FieldTypeDetail from "metabase/reference/components/FieldTypeDetail";
+import UsefulQuestions from "metabase/reference/components/UsefulQuestions";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+import { getQuestionUrl } from "../utils";
+
+import {
+  getFieldBySegment,
+  getTable,
+  getError,
+  getLoading,
+  getUser,
+  getIsEditing,
+  getForeignKeys,
+  getIsFormulaExpanded,
+} from "../selectors";
+
+const interestingQuestions = (table, field) => {
+  return [
+    {
+      text: t`Number of ${table && table.display_name} grouped by ${
+        field.display_name
+      }`,
+      icon: "number",
+      link: getQuestionUrl({
+        dbId: table && table.db_id,
+        tableId: table.id,
+        fieldId: field.id,
+        getCount: true,
+      }),
+    },
+    {
+      text: t`All distinct values of ${field.display_name}`,
+      icon: "table2",
+      link: getQuestionUrl({
+        dbId: table && table.db_id,
+        tableId: table.id,
+        fieldId: field.id,
+      }),
+    },
+  ];
+};
+
+const mapStateToProps = (state, props) => {
+  const entity = getFieldBySegment(state, props) || {};
+
+  return {
+    entity,
+    table: getTable(state, props),
+    loading: getLoading(state, props),
+    // naming this 'error' will conflict with redux form
+    loadingError: getError(state, props),
+    user: getUser(state, props),
+    foreignKeys: getForeignKeys(state, props),
+    isEditing: getIsEditing(state, props),
+    isFormulaExpanded: getIsFormulaExpanded(state, props),
+  };
+};
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+  onSubmit: actions.rUpdateSegmentFieldDetail,
+};
+
+const propTypes = {
+  style: PropTypes.object.isRequired,
+  entity: PropTypes.object.isRequired,
+  table: PropTypes.object,
+  user: PropTypes.object.isRequired,
+  foreignKeys: PropTypes.object,
+  isEditing: PropTypes.bool,
+  startEditing: PropTypes.func.isRequired,
+  endEditing: PropTypes.func.isRequired,
+  startLoading: PropTypes.func.isRequired,
+  endLoading: PropTypes.func.isRequired,
+  setError: PropTypes.func.isRequired,
+  updateField: PropTypes.func.isRequired,
+  loading: PropTypes.bool,
+  loadingError: PropTypes.object,
+  onSubmit: PropTypes.func.isRequired,
+};
+
+const SegmentFieldDetail = props => {
+  const {
+    style,
+    entity,
+    table,
+    loadingError,
+    loading,
+    user,
+    foreignKeys,
+    isEditing,
+    startEditing,
+    endEditing,
+    onSubmit,
+  } = props;
+
+  const {
+    isSubmitting,
+    getFieldProps,
+    getFieldMeta,
+    handleSubmit,
+    handleReset,
+  } = useFormik({
+    initialValues: {},
+    onSubmit: fields => onSubmit(fields, { ...props, resetForm: handleReset }),
+  });
+
+  const getFormField = name => ({
+    ...getFieldProps(name),
+    ...getFieldMeta(name),
+  });
+
+  return (
+    <form style={style} className="full" onSubmit={handleSubmit}>
+      {isEditing && (
+        <EditHeader
+          hasRevisionHistory={false}
+          onSubmit={handleSubmit}
+          endEditing={endEditing}
+          reinitializeForm={handleReset()}
+          submitting={isSubmitting}
+          revisionMessageFormField={getFormField("revision_message")}
+        />
+      )}
+      <EditableReferenceHeader
+        entity={entity}
+        table={table}
+        headerIcon="field"
+        name={t`Details`}
+        type="field"
+        user={user}
+        isEditing={isEditing}
+        hasSingleSchema={false}
+        hasDisplayName={true}
+        startEditing={startEditing}
+        displayNameFormField={getFormField("display_name")}
+        nameFormField={getFormField("name")}
+      />
+      <LoadingAndErrorWrapper
+        loading={!loadingError && loading}
+        error={loadingError}
+      >
+        {() => (
+          <div className="wrapper">
+            <div className="pl3 py2 mb4 bg-white bordered">
+              <List>
+                <li className="relative">
+                  <Detail
+                    id="description"
+                    name={t`Description`}
+                    description={entity.description}
+                    placeholder={t`No description yet`}
+                    isEditing={isEditing}
+                    field={getFormField("description")}
+                  />
+                </li>
+                {!isEditing && (
+                  <li className="relative">
+                    <Detail
+                      id="name"
+                      name={t`Actual name in database`}
+                      description={entity.name}
+                      subtitleClass={S.tableActualName}
+                    />
+                  </li>
+                )}
+                <li className="relative">
+                  <Detail
+                    id="points_of_interest"
+                    name={t`Why this field is interesting`}
+                    description={entity.points_of_interest}
+                    placeholder={t`Nothing interesting yet`}
+                    isEditing={isEditing}
+                    field={getFormField("points_of_interest")}
+                  />
+                </li>
+                <li className="relative">
+                  <Detail
+                    id="caveats"
+                    name={t`Things to be aware of about this field`}
+                    description={entity.caveats}
+                    placeholder={t`Nothing to be aware of yet`}
+                    isEditing={isEditing}
+                    field={getFormField("caveats")}
+                  />
+                </li>
+
+                {!isEditing && (
+                  <li className="relative">
+                    <Detail
+                      id="base_type"
+                      name={t`Data type`}
+                      description={entity.base_type}
+                    />
+                  </li>
+                )}
+                <li className="relative">
+                  <FieldTypeDetail
+                    field={entity}
+                    foreignKeys={foreignKeys}
+                    fieldTypeFormField={getFormField("semantic_type")}
+                    foreignKeyFormField={getFormField("fk_target_field_id")}
+                    isEditing={isEditing}
+                  />
+                </li>
+                {!isEditing && (
+                  <li className="relative">
+                    <UsefulQuestions
+                      questions={interestingQuestions(
+                        props.table,
+                        props.entity,
+                      )}
+                    />
+                  </li>
+                )}
+              </List>
+            </div>
+          </div>
+        )}
+      </LoadingAndErrorWrapper>
+    </form>
+  );
+};
+
+SegmentFieldDetail.propTypes = propTypes;
+
+export default connect(mapStateToProps, mapDispatchToProps)(SegmentFieldDetail);
diff --git a/frontend/src/metabase/reference/segments/SegmentFieldDetailContainer.jsx b/frontend/src/metabase/reference/segments/SegmentFieldDetailContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e530f438d21b67e35d58e465478c2253009c163d
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentFieldDetailContainer.jsx
@@ -0,0 +1,79 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+import SegmentFieldDetail from "metabase/reference/segments/SegmentFieldDetail";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import {
+  getSegment,
+  getSegmentId,
+  getField,
+  getDatabaseId,
+  getIsEditing,
+} from "../selectors";
+import SegmentFieldSidebar from "./SegmentFieldSidebar";
+
+const mapStateToProps = (state, props) => ({
+  segment: getSegment(state, props),
+  segmentId: getSegmentId(state, props),
+  field: getField(state, props),
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class SegmentFieldDetailContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    segment: PropTypes.object.isRequired,
+    segmentId: PropTypes.number.isRequired,
+    field: PropTypes.object.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchSegmentFields(this.props, this.props.segmentId);
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { segment, field, isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<SegmentFieldSidebar segment={segment} field={field} />}
+      >
+        <SegmentFieldDetail {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(SegmentFieldDetailContainer);
diff --git a/frontend/src/metabase/reference/segments/SegmentFieldList.jsx b/frontend/src/metabase/reference/segments/SegmentFieldList.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..5c8578a2fdcf3b19c140ae7867c319ebc41fa1ba
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentFieldList.jsx
@@ -0,0 +1,185 @@
+/* eslint "react/prop-types": "warn" */
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { useFormik } from "formik";
+import { t } from "ttag";
+import cx from "classnames";
+import S from "metabase/components/List/List.css";
+import R from "metabase/reference/Reference.css";
+import F from "metabase/reference/components/Field.css";
+
+import Field from "metabase/reference/components/Field";
+import List from "metabase/components/List";
+import EmptyState from "metabase/components/EmptyState";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import EditHeader from "metabase/reference/components/EditHeader";
+import EditableReferenceHeader from "metabase/reference/components/EditableReferenceHeader";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+import { getIconForField } from "metabase-lib/metadata/utils/fields";
+import {
+  getError,
+  getFieldsBySegment,
+  getForeignKeys,
+  getIsEditing,
+  getLoading,
+  getSegment,
+  getUser,
+} from "../selectors";
+
+const emptyStateData = {
+  message: t`Fields in this table will appear here as they're added`,
+  icon: "fields",
+};
+
+const mapStateToProps = (state, props) => {
+  const data = getFieldsBySegment(state, props);
+  return {
+    segment: getSegment(state, props),
+    entities: data,
+    foreignKeys: getForeignKeys(state, props),
+    loading: getLoading(state, props),
+    loadingError: getError(state, props),
+    user: getUser(state, props),
+    isEditing: getIsEditing(state, props),
+  };
+};
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+  onSubmit: actions.rUpdateFields,
+};
+
+const propTypes = {
+  segment: PropTypes.object.isRequired,
+  style: PropTypes.object.isRequired,
+  entities: PropTypes.object.isRequired,
+  foreignKeys: PropTypes.object.isRequired,
+  isEditing: PropTypes.bool,
+  startEditing: PropTypes.func.isRequired,
+  endEditing: PropTypes.func.isRequired,
+  startLoading: PropTypes.func.isRequired,
+  endLoading: PropTypes.func.isRequired,
+  setError: PropTypes.func.isRequired,
+  updateField: PropTypes.func.isRequired,
+  user: PropTypes.object.isRequired,
+  loading: PropTypes.bool,
+  loadingError: PropTypes.object,
+  onSubmit: PropTypes.func,
+};
+
+const SegmentFieldList = props => {
+  const {
+    segment,
+    style,
+    entities,
+    foreignKeys,
+    loadingError,
+    loading,
+    user,
+    isEditing,
+    startEditing,
+    endEditing,
+    onSubmit,
+  } = props;
+
+  const {
+    isSubmitting,
+    getFieldProps,
+    getFieldMeta,
+    handleSubmit,
+    handleReset,
+  } = useFormik({
+    initialValues: {},
+    onSubmit: fields =>
+      onSubmit(entities, fields, { ...props, resetForm: handleReset }),
+  });
+
+  const getFormField = name => ({
+    ...getFieldProps(name),
+    ...getFieldMeta(name),
+  });
+
+  const getNestedFormField = id => ({
+    display_name: getFormField(`${id}.display_name`),
+    semantic_type: getFormField(`${id}.semantic_type`),
+    fk_target_field_id: getFormField(`${id}.fk_target_field_id`),
+  });
+
+  return (
+    <form style={style} className="full" onSubmit={handleSubmit}>
+      {isEditing && (
+        <EditHeader
+          hasRevisionHistory={false}
+          reinitializeForm={handleReset}
+          endEditing={endEditing}
+          submitting={isSubmitting}
+        />
+      )}
+      <EditableReferenceHeader
+        type="segment"
+        headerIcon="segment"
+        name={t`Fields in ${segment.name}`}
+        user={user}
+        isEditing={isEditing}
+        startEditing={startEditing}
+      />
+      <LoadingAndErrorWrapper
+        loading={!loadingError && loading}
+        error={loadingError}
+      >
+        {() =>
+          Object.keys(entities).length > 0 ? (
+            <div className="wrapper">
+              <div className="pl4 pb2 mb4 bg-white rounded bordered">
+                <div className={S.item}>
+                  <div className={R.columnHeader}>
+                    <div className={cx(S.itemTitle, F.fieldNameTitle)}>
+                      {t`Field name`}
+                    </div>
+                    <div className={cx(S.itemTitle, F.fieldType)}>
+                      {t`Field type`}
+                    </div>
+                    <div className={cx(S.itemTitle, F.fieldDataType)}>
+                      {t`Data type`}
+                    </div>
+                  </div>
+                </div>
+                <List>
+                  {Object.values(entities).map(
+                    entity =>
+                      entity &&
+                      entity.id &&
+                      entity.name && (
+                        <li className="relative" key={entity.id}>
+                          <Field
+                            field={entity}
+                            foreignKeys={foreignKeys}
+                            url={`/reference/segments/${segment.id}/fields/${entity.id}`}
+                            icon={getIconForField(entity)}
+                            isEditing={isEditing}
+                            formField={getNestedFormField(entity.id)}
+                          />
+                        </li>
+                      ),
+                  )}
+                </List>
+              </div>
+            </div>
+          ) : (
+            <div className={S.empty}>
+              <EmptyState {...emptyStateData} />
+            </div>
+          )
+        }
+      </LoadingAndErrorWrapper>
+    </form>
+  );
+};
+
+SegmentFieldList.propTypes = propTypes;
+
+export default connect(mapStateToProps, mapDispatchToProps)(SegmentFieldList);
diff --git a/frontend/src/metabase/reference/segments/SegmentFieldListContainer.jsx b/frontend/src/metabase/reference/segments/SegmentFieldListContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..8ebda9b275458a1ea615b8bae913220fe2ffeb2f
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentFieldListContainer.jsx
@@ -0,0 +1,79 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+import SegmentFieldList from "metabase/reference/segments/SegmentFieldList";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import {
+  getUser,
+  getSegment,
+  getSegmentId,
+  getDatabaseId,
+  getIsEditing,
+} from "../selectors";
+import SegmentSidebar from "./SegmentSidebar";
+
+const mapStateToProps = (state, props) => ({
+  user: getUser(state, props),
+  segment: getSegment(state, props),
+  segmentId: getSegmentId(state, props),
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class SegmentFieldListContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    user: PropTypes.object.isRequired,
+    segment: PropTypes.object.isRequired,
+    segmentId: PropTypes.number.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchSegmentFields(this.props, this.props.segmentId);
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { user, segment, isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<SegmentSidebar segment={segment} user={user} />}
+      >
+        <SegmentFieldList {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(SegmentFieldListContainer);
diff --git a/frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx b/frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..65b30b6e485621fb66c96b7de264a54fb4422d71
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx
@@ -0,0 +1,42 @@
+/* eslint "react/prop-types": "warn" */
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { t } from "ttag";
+import cx from "classnames";
+import S from "metabase/components/Sidebar.css";
+import Breadcrumbs from "metabase/components/Breadcrumbs";
+import SidebarItem from "metabase/components/SidebarItem";
+
+const SegmentFieldSidebar = ({ segment, field, style, className }) => (
+  <div className={cx(S.sidebar, className)} style={style}>
+    <ul className="mx3">
+      <div className={S.breadcrumbs}>
+        <Breadcrumbs
+          className="py4"
+          crumbs={[
+            [t`Segments`, "/reference/segments"],
+            [segment.name, `/reference/segments/${segment.id}`],
+            [field.name],
+          ]}
+          inSidebar={true}
+          placeholder={t`Data Reference`}
+        />
+      </div>
+      <SidebarItem
+        key={`/reference/segments/${segment.id}/fields/${field.id}`}
+        href={`/reference/segments/${segment.id}/fields/${field.id}`}
+        icon="document"
+        name={t`Details`}
+      />
+    </ul>
+  </div>
+);
+
+SegmentFieldSidebar.propTypes = {
+  segment: PropTypes.object,
+  field: PropTypes.object,
+  className: PropTypes.string,
+  style: PropTypes.object,
+};
+
+export default memo(SegmentFieldSidebar);
diff --git a/frontend/src/metabase/reference/segments/SegmentList.jsx b/frontend/src/metabase/reference/segments/SegmentList.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..6ab37213572346795df0c948e300eb497aea42df
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentList.jsx
@@ -0,0 +1,93 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { t } from "ttag";
+import MetabaseSettings from "metabase/lib/settings";
+
+import S from "metabase/components/List/List.css";
+
+import List from "metabase/components/List";
+import ListItem from "metabase/components/ListItem";
+import AdminAwareEmptyState from "metabase/components/AdminAwareEmptyState";
+
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import * as metadataActions from "metabase/redux/metadata";
+import ReferenceHeader from "../components/ReferenceHeader";
+
+import { getSegments, getError, getLoading } from "../selectors";
+
+const emptyStateData = {
+  title: t`Segments are interesting subsets of tables`,
+  adminMessage: t`Defining common segments for your team makes it even easier to ask questions`,
+  message: t`Segments will appear here once your admins have created some`,
+  image: "app/assets/img/segments-list",
+  adminAction: t`Learn how to create segments`,
+  adminLink: MetabaseSettings.docsUrl(
+    "data-modeling/segments-and-metrics",
+    "creating-a-segment",
+  ),
+};
+
+const mapStateToProps = (state, props) => ({
+  entities: getSegments(state, props),
+  loading: getLoading(state, props),
+  loadingError: getError(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+};
+
+class SegmentList extends Component {
+  static propTypes = {
+    style: PropTypes.object.isRequired,
+    entities: PropTypes.object.isRequired,
+    loading: PropTypes.bool,
+    loadingError: PropTypes.object,
+  };
+
+  render() {
+    const { entities, style, loadingError, loading } = this.props;
+
+    return (
+      <div style={style} className="full">
+        <ReferenceHeader name={t`Segments`} />
+        <LoadingAndErrorWrapper
+          loading={!loadingError && loading}
+          error={loadingError}
+        >
+          {() =>
+            Object.keys(entities).length > 0 ? (
+              <div className="wrapper wrapper--trim">
+                <List>
+                  {Object.values(entities).map(
+                    entity =>
+                      entity &&
+                      entity.id &&
+                      entity.name && (
+                        <ListItem
+                          key={entity.id}
+                          name={entity.display_name || entity.name}
+                          description={entity.description}
+                          url={`/reference/segments/${entity.id}`}
+                          icon="segment"
+                        />
+                      ),
+                  )}
+                </List>
+              </div>
+            ) : (
+              <div className={S.empty}>
+                <AdminAwareEmptyState {...emptyStateData} />
+              </div>
+            )
+          }
+        </LoadingAndErrorWrapper>
+      </div>
+    );
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(SegmentList);
diff --git a/frontend/src/metabase/reference/segments/SegmentListContainer.jsx b/frontend/src/metabase/reference/segments/SegmentListContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..47fb171140aece70c9617c6e59ceaa485e88791a
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentListContainer.jsx
@@ -0,0 +1,67 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import BaseSidebar from "metabase/reference/guide/BaseSidebar";
+import SidebarLayout from "metabase/components/SidebarLayout";
+import SegmentList from "metabase/reference/segments/SegmentList";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import { getDatabaseId, getIsEditing } from "../selectors";
+
+const mapStateToProps = (state, props) => ({
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class SegmentListContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchSegments(this.props);
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<BaseSidebar />}
+      >
+        <SegmentList {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(SegmentListContainer);
diff --git a/frontend/src/metabase/reference/segments/SegmentQuestions.jsx b/frontend/src/metabase/reference/segments/SegmentQuestions.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..22cec2a2b59ae5949a1aed282feeff74d79cfc5e
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentQuestions.jsx
@@ -0,0 +1,115 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import moment from "moment-timezone";
+import { t } from "ttag";
+import visualizations from "metabase/visualizations";
+import * as Urls from "metabase/lib/urls";
+
+import S from "metabase/components/List/List.css";
+
+import List from "metabase/components/List";
+import ListItem from "metabase/components/ListItem";
+import AdminAwareEmptyState from "metabase/components/AdminAwareEmptyState";
+
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import * as metadataActions from "metabase/redux/metadata";
+import ReferenceHeader from "../components/ReferenceHeader";
+
+import { getQuestionUrl } from "../utils";
+
+import {
+  getSegmentQuestions,
+  getError,
+  getLoading,
+  getTableBySegment,
+  getSegment,
+} from "../selectors";
+
+const emptyStateData = (table, segment) => {
+  return {
+    message: t`Questions about this segment will appear here as they're added`,
+    icon: "folder",
+    action: t`Ask a question`,
+    link: getQuestionUrl({
+      dbId: table && table.db_id,
+      tableId: segment.table_id,
+      segmentId: segment.id,
+    }),
+  };
+};
+const mapStateToProps = (state, props) => ({
+  segment: getSegment(state, props),
+  table: getTableBySegment(state, props),
+  entities: getSegmentQuestions(state, props),
+  loading: getLoading(state, props),
+  loadingError: getError(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+};
+
+class SegmentQuestions extends Component {
+  static propTypes = {
+    table: PropTypes.object.isRequired,
+    segment: PropTypes.object.isRequired,
+    style: PropTypes.object.isRequired,
+    entities: PropTypes.object.isRequired,
+    loading: PropTypes.bool,
+    loadingError: PropTypes.object,
+  };
+
+  render() {
+    const { entities, style, loadingError, loading } = this.props;
+
+    return (
+      <div style={style} className="full">
+        <ReferenceHeader
+          name={t`Questions about ${this.props.segment.name}`}
+          type="questions"
+          headerIcon="segment"
+        />
+        <LoadingAndErrorWrapper
+          loading={!loadingError && loading}
+          error={loadingError}
+        >
+          {() =>
+            Object.keys(entities).length > 0 ? (
+              <div className="wrapper wrapper--trim">
+                <List>
+                  {Object.values(entities).map(
+                    entity =>
+                      entity &&
+                      entity.id &&
+                      entity.name && (
+                        <ListItem
+                          key={entity.id}
+                          name={entity.display_name || entity.name}
+                          description={t`Created ${moment(
+                            entity.created_at,
+                          ).fromNow()} by ${entity.creator.common_name}`}
+                          url={Urls.question(entity)}
+                          icon={visualizations.get(entity.display).iconName}
+                        />
+                      ),
+                  )}
+                </List>
+              </div>
+            ) : (
+              <div className={S.empty}>
+                <AdminAwareEmptyState
+                  {...emptyStateData(this.props.table, this.props.segment)}
+                />
+              </div>
+            )
+          }
+        </LoadingAndErrorWrapper>
+      </div>
+    );
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(SegmentQuestions);
diff --git a/frontend/src/metabase/reference/segments/SegmentQuestionsContainer.jsx b/frontend/src/metabase/reference/segments/SegmentQuestionsContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c15b912fafe814766c80a12e54d3fc60a55eeac0
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentQuestionsContainer.jsx
@@ -0,0 +1,85 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+import SegmentQuestions from "metabase/reference/segments/SegmentQuestions";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import Questions from "metabase/entities/questions";
+import {
+  getUser,
+  getSegment,
+  getSegmentId,
+  getDatabaseId,
+  getIsEditing,
+} from "../selectors";
+
+import SegmentSidebar from "./SegmentSidebar";
+
+const mapStateToProps = (state, props) => ({
+  user: getUser(state, props),
+  segment: getSegment(state, props),
+  segmentId: getSegmentId(state, props),
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  fetchQuestions: Questions.actions.fetchList,
+  ...metadataActions,
+  ...actions,
+};
+
+class SegmentQuestionsContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    user: PropTypes.object.isRequired,
+    segment: PropTypes.object.isRequired,
+    segmentId: PropTypes.number.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchSegmentQuestions(
+      this.props,
+      this.props.segmentId,
+    );
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { user, segment, isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<SegmentSidebar segment={segment} user={user} />}
+      >
+        <SegmentQuestions {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(SegmentQuestionsContainer);
diff --git a/frontend/src/metabase/reference/segments/SegmentRevisions.jsx b/frontend/src/metabase/reference/segments/SegmentRevisions.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..6a17f78d6d638fb0c7b1bf587a74461380041393
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentRevisions.jsx
@@ -0,0 +1,130 @@
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { t } from "ttag";
+import { getIn } from "icepick";
+
+import S from "metabase/components/List/List.css";
+
+import * as metadataActions from "metabase/redux/metadata";
+import { assignUserColors } from "metabase/lib/formatting";
+
+import Revision from "metabase/admin/datamodel/components/revisions/Revision";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+import EmptyState from "metabase/components/EmptyState";
+import {
+  getSegmentRevisions,
+  getMetric,
+  getSegment,
+  getTables,
+  getUser,
+  getLoading,
+  getError,
+} from "../selectors";
+import ReferenceHeader from "../components/ReferenceHeader";
+
+const emptyStateData = {
+  message: t`There are no revisions for this segment`,
+};
+
+const mapStateToProps = (state, props) => {
+  return {
+    revisions: getSegmentRevisions(state, props),
+    metric: getMetric(state, props),
+    segment: getSegment(state, props),
+    tables: getTables(state, props),
+    user: getUser(state, props),
+    loading: getLoading(state, props),
+    loadingError: getError(state, props),
+  };
+};
+
+const mapDispatchToProps = {
+  ...metadataActions,
+};
+
+class SegmentRevisions extends Component {
+  static propTypes = {
+    style: PropTypes.object.isRequired,
+    revisions: PropTypes.object.isRequired,
+    metric: PropTypes.object.isRequired,
+    segment: PropTypes.object.isRequired,
+    tables: PropTypes.object.isRequired,
+    user: PropTypes.object.isRequired,
+    loading: PropTypes.bool,
+    loadingError: PropTypes.object,
+  };
+
+  render() {
+    const {
+      style,
+      revisions,
+      metric,
+      segment,
+      tables,
+      user,
+      loading,
+      loadingError,
+    } = this.props;
+
+    const entity = metric.id ? metric : segment;
+
+    const userColorAssignments =
+      user && Object.keys(revisions).length > 0
+        ? assignUserColors(
+            Object.values(revisions).map(revision =>
+              getIn(revision, ["user", "id"]),
+            ),
+            user.id,
+          )
+        : {};
+
+    return (
+      <div style={style} className="full">
+        <ReferenceHeader
+          name={t`Revision history for ${this.props.segment.name}`}
+          headerIcon="segment"
+        />
+        <LoadingAndErrorWrapper
+          loading={!loadingError && loading}
+          error={loadingError}
+        >
+          {() =>
+            Object.keys(revisions).length > 0 && tables[entity.table_id] ? (
+              <div className="wrapper">
+                <div className="px3 py3 mb4 bg-white bordered">
+                  <div>
+                    {Object.values(revisions)
+                      .map(revision =>
+                        revision && revision.diff ? (
+                          <Revision
+                            key={revision.id}
+                            revision={revision || {}}
+                            tableMetadata={tables[entity.table_id] || {}}
+                            objectName={entity.name}
+                            currentUser={user || {}}
+                            userColor={
+                              userColorAssignments[
+                                getIn(revision, ["user", "id"])
+                              ]
+                            }
+                          />
+                        ) : null,
+                      )
+                      .reverse()}
+                  </div>
+                </div>
+              </div>
+            ) : (
+              <div className={S.empty}>
+                <EmptyState {...emptyStateData} />
+              </div>
+            )
+          }
+        </LoadingAndErrorWrapper>
+      </div>
+    );
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(SegmentRevisions);
diff --git a/frontend/src/metabase/reference/segments/SegmentRevisionsContainer.jsx b/frontend/src/metabase/reference/segments/SegmentRevisionsContainer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..330bb3aa2c0075cc7d8f93050d186ae71e46b3e2
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentRevisionsContainer.jsx
@@ -0,0 +1,82 @@
+/* eslint "react/prop-types": "warn" */
+import { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import SidebarLayout from "metabase/components/SidebarLayout";
+import SegmentRevisions from "metabase/reference/segments/SegmentRevisions";
+
+import * as metadataActions from "metabase/redux/metadata";
+import * as actions from "metabase/reference/reference";
+
+import {
+  getUser,
+  getSegment,
+  getSegmentId,
+  getDatabaseId,
+  getIsEditing,
+} from "../selectors";
+import SegmentSidebar from "./SegmentSidebar";
+
+const mapStateToProps = (state, props) => ({
+  user: getUser(state, props),
+  segment: getSegment(state, props),
+  segmentId: getSegmentId(state, props),
+  databaseId: getDatabaseId(state, props),
+  isEditing: getIsEditing(state, props),
+});
+
+const mapDispatchToProps = {
+  ...metadataActions,
+  ...actions,
+};
+
+class SegmentRevisionsContainer extends Component {
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    location: PropTypes.object.isRequired,
+    databaseId: PropTypes.number.isRequired,
+    user: PropTypes.object.isRequired,
+    segment: PropTypes.object.isRequired,
+    segmentId: PropTypes.number.isRequired,
+    isEditing: PropTypes.bool,
+  };
+
+  async fetchContainerData() {
+    await actions.wrappedFetchSegmentRevisions(
+      this.props,
+      this.props.segmentId,
+    );
+  }
+
+  UNSAFE_componentWillMount() {
+    this.fetchContainerData();
+  }
+
+  UNSAFE_componentWillReceiveProps(newProps) {
+    if (this.props.location.pathname === newProps.location.pathname) {
+      return;
+    }
+
+    actions.clearState(newProps);
+  }
+
+  render() {
+    const { user, segment, isEditing } = this.props;
+
+    return (
+      <SidebarLayout
+        className="flex-full relative"
+        style={isEditing ? { paddingTop: "43px" } : {}}
+        sidebar={<SegmentSidebar segment={segment} user={user} />}
+      >
+        <SegmentRevisions {...this.props} />
+      </SidebarLayout>
+    );
+  }
+}
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(SegmentRevisionsContainer);
diff --git a/frontend/src/metabase/reference/segments/SegmentSidebar.jsx b/frontend/src/metabase/reference/segments/SegmentSidebar.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..ca1cdc4042ee2c9fd607a40b2a30c874c6fd2251
--- /dev/null
+++ b/frontend/src/metabase/reference/segments/SegmentSidebar.jsx
@@ -0,0 +1,72 @@
+/* eslint "react/prop-types": "warn" */
+import { memo } from "react";
+import PropTypes from "prop-types";
+import { t } from "ttag";
+import cx from "classnames";
+
+import MetabaseSettings from "metabase/lib/settings";
+
+import Breadcrumbs from "metabase/components/Breadcrumbs";
+import SidebarItem from "metabase/components/SidebarItem";
+
+import S from "metabase/components/Sidebar.css";
+
+const SegmentSidebar = ({ segment, user, style, className }) => (
+  <div className={cx(S.sidebar, className)} style={style}>
+    <ul>
+      <div className={S.breadcrumbs}>
+        <Breadcrumbs
+          className="py4 ml3"
+          crumbs={[[t`Segments`, "/reference/segments"], [segment.name]]}
+          inSidebar={true}
+          placeholder={t`Data Reference`}
+        />
+      </div>
+      <ol className="mx3">
+        <SidebarItem
+          key={`/reference/segments/${segment.id}`}
+          href={`/reference/segments/${segment.id}`}
+          icon="document"
+          name={t`Details`}
+        />
+        <SidebarItem
+          key={`/reference/segments/${segment.id}/fields`}
+          href={`/reference/segments/${segment.id}/fields`}
+          icon="field"
+          name={t`Fields in this segment`}
+        />
+        <SidebarItem
+          key={`/reference/segments/${segment.id}/questions`}
+          href={`/reference/segments/${segment.id}/questions`}
+          icon="folder"
+          name={t`Questions about this segment`}
+        />
+        {MetabaseSettings.get("enable-xrays") && (
+          <SidebarItem
+            key={`/auto/dashboard/segment/${segment.id}`}
+            href={`/auto/dashboard/segment/${segment.id}`}
+            icon="bolt"
+            name={t`X-ray this segment`}
+          />
+        )}
+        {user && user.is_superuser && (
+          <SidebarItem
+            key={`/reference/segments/${segment.id}/revisions`}
+            href={`/reference/segments/${segment.id}/revisions`}
+            icon="history"
+            name={t`Revision history`}
+          />
+        )}
+      </ol>
+    </ul>
+  </div>
+);
+
+SegmentSidebar.propTypes = {
+  segment: PropTypes.object,
+  user: PropTypes.object,
+  className: PropTypes.string,
+  style: PropTypes.object,
+};
+
+export default memo(SegmentSidebar);
diff --git a/frontend/src/metabase/reference/selectors.js b/frontend/src/metabase/reference/selectors.js
new file mode 100644
index 0000000000000000000000000000000000000000..4cea36d1454e5c1c778a334035fc293ba6b5ec3a
--- /dev/null
+++ b/frontend/src/metabase/reference/selectors.js
@@ -0,0 +1,201 @@
+import { createSelector } from "@reduxjs/toolkit";
+import { assoc, getIn } from "icepick";
+
+import Dashboards from "metabase/entities/dashboards";
+
+import { resourceListToMap } from "metabase/lib/redux";
+import {
+  getShallowDatabases as getDatabases,
+  getShallowTables as getTables,
+  getShallowFields as getFields,
+  getShallowMetrics as getMetrics,
+  getShallowSegments as getSegments,
+} from "metabase/selectors/metadata";
+
+import Question from "metabase-lib/Question";
+
+import { idsToObjectMap, databaseToForeignKeys } from "./utils";
+
+// import { getDatabases, getTables, getFields, getMetrics, getSegments } from "metabase/selectors/metadata";
+
+export {
+  getShallowDatabases as getDatabases,
+  getShallowTables as getTables,
+  getShallowFields as getFields,
+  getShallowMetrics as getMetrics,
+  getShallowSegments as getSegments,
+} from "metabase/selectors/metadata";
+
+export const getUser = (state, props) => state.currentUser;
+
+export const getMetricId = (state, props) =>
+  Number.parseInt(props.params.metricId);
+export const getMetric = createSelector(
+  [getMetricId, getMetrics],
+  (metricId, metrics) => metrics[metricId] || { id: metricId },
+);
+
+export const getSegmentId = (state, props) =>
+  Number.parseInt(props.params.segmentId);
+export const getSegment = createSelector(
+  [getSegmentId, getSegments],
+  (segmentId, segments) => segments[segmentId] || { id: segmentId },
+);
+
+export const getDatabaseId = (state, props) =>
+  Number.parseInt(props.params.databaseId);
+
+export const getDatabase = createSelector(
+  [getDatabaseId, getDatabases],
+  (databaseId, databases) => databases[databaseId] || { id: databaseId },
+);
+
+export const getTableId = (state, props) =>
+  Number.parseInt(props.params.tableId);
+// export const getTableId = (state, props) => Number.parseInt(props.params.tableId);
+export const getTablesByDatabase = createSelector(
+  [getTables, getDatabase],
+  (tables, database) =>
+    tables && database && database.tables
+      ? idsToObjectMap(database.tables, tables)
+      : {},
+);
+export const getTableBySegment = createSelector(
+  [getSegment, getTables],
+  (segment, tables) =>
+    segment && segment.table_id ? tables[segment.table_id] : {},
+);
+const getTableByMetric = createSelector(
+  [getMetric, getTables],
+  (metric, tables) =>
+    metric && metric.table_id ? tables[metric.table_id] : {},
+);
+export const getTable = createSelector(
+  [
+    getTableId,
+    getTables,
+    getMetricId,
+    getTableByMetric,
+    getSegmentId,
+    getTableBySegment,
+  ],
+  (tableId, tables, metricId, tableByMetric, segmentId, tableBySegment) =>
+    tableId
+      ? tables[tableId] || { id: tableId }
+      : metricId
+      ? tableByMetric
+      : segmentId
+      ? tableBySegment
+      : {},
+);
+
+export const getFieldId = (state, props) =>
+  Number.parseInt(props.params.fieldId);
+export const getFieldsByTable = createSelector(
+  [getTable, getFields],
+  (table, fields) =>
+    table && table.fields ? idsToObjectMap(table.fields, fields) : {},
+);
+export const getFieldsBySegment = createSelector(
+  [getTableBySegment, getFields],
+  (table, fields) =>
+    table && table.fields ? idsToObjectMap(table.fields, fields) : {},
+);
+export const getField = createSelector(
+  [getFieldId, getFields],
+  (fieldId, fields) => fields[fieldId] || { id: fieldId },
+);
+export const getFieldBySegment = createSelector(
+  [getFieldId, getFieldsBySegment],
+  (fieldId, fields) => fields[fieldId] || { id: fieldId },
+);
+
+const getQuestions = (state, props) =>
+  getIn(state, ["entities", "questions"]) || {};
+
+export const getMetricQuestions = createSelector(
+  [getMetricId, getQuestions],
+  (metricId, questions) =>
+    Object.values(questions)
+      .filter(question => new Question(question).usesMetric(metricId))
+      .reduce((map, question) => assoc(map, question.id, question), {}),
+);
+
+const getRevisions = (state, props) => state.revisions;
+
+export const getMetricRevisions = createSelector(
+  [getMetricId, getRevisions],
+  (metricId, revisions) => getIn(revisions, ["metric", metricId]) || {},
+);
+
+export const getSegmentRevisions = createSelector(
+  [getSegmentId, getRevisions],
+  (segmentId, revisions) => getIn(revisions, ["segment", segmentId]) || {},
+);
+
+export const getSegmentQuestions = createSelector(
+  [getSegmentId, getQuestions],
+  (segmentId, questions) =>
+    Object.values(questions)
+      .filter(question => new Question(question).usesSegment(segmentId))
+      .reduce((map, question) => assoc(map, question.id, question), {}),
+);
+
+export const getTableQuestions = createSelector(
+  [getTable, getQuestions],
+  (table, questions) =>
+    Object.values(questions).filter(question => question.table_id === table.id),
+);
+
+const getDatabaseBySegment = createSelector(
+  [getSegment, getTables, getDatabases],
+  (segment, tables, databases) =>
+    (segment &&
+      segment.table_id &&
+      tables[segment.table_id] &&
+      databases[tables[segment.table_id].db_id]) ||
+    {},
+);
+
+const getForeignKeysBySegment = createSelector(
+  [getDatabaseBySegment],
+  databaseToForeignKeys,
+);
+
+const getForeignKeysByDatabase = createSelector(
+  [getDatabase],
+  databaseToForeignKeys,
+);
+
+export const getForeignKeys = createSelector(
+  [getSegmentId, getForeignKeysBySegment, getForeignKeysByDatabase],
+  (segmentId, foreignKeysBySegment, foreignKeysByDatabase) =>
+    segmentId ? foreignKeysBySegment : foreignKeysByDatabase,
+);
+
+export const getLoading = (state, props) => state.reference.isLoading;
+
+export const getError = (state, props) => state.reference.error;
+
+export const getHasSingleSchema = createSelector(
+  [getTablesByDatabase],
+  tables =>
+    tables && Object.keys(tables).length > 0
+      ? Object.values(tables).every(
+          (table, index, tables) => table.schema_name === tables[0].schema,
+        )
+      : true,
+);
+
+export const getIsEditing = (state, props) => state.reference.isEditing;
+
+export const getIsFormulaExpanded = (state, props) =>
+  state.reference.isFormulaExpanded;
+
+export const getDashboards = (state, props) => {
+  const list = Dashboards.selectors.getList(state);
+  return list && resourceListToMap(list);
+};
+
+export const getIsDashboardModalOpen = (state, props) =>
+  state.reference.isDashboardModalOpen;
diff --git a/frontend/src/metabase/reference/utils.js b/frontend/src/metabase/reference/utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..d627d8141784722086ff71df1bcf61d34b0f6664
--- /dev/null
+++ b/frontend/src/metabase/reference/utils.js
@@ -0,0 +1,104 @@
+import { assoc, assocIn, chain } from "icepick";
+
+import { titleize, humanize } from "metabase/lib/formatting";
+import { startNewCard } from "metabase/lib/card";
+import * as Urls from "metabase/lib/urls";
+import { isTypePK } from "metabase-lib/types/utils/isa";
+
+export const idsToObjectMap = (ids, objects) =>
+  ids
+    .map(id => objects[id])
+    .reduce((map, object) => ({ ...map, [object.id]: object }), {});
+// recursive freezing done by assoc here is too expensive
+// hangs browser for large databases
+// .reduce((map, object) => assoc(map, object.id, object), {});
+
+export const filterUntouchedFields = (fields, entity = {}) =>
+  Object.keys(fields)
+    .filter(key => fields[key] !== undefined && entity[key] !== fields[key])
+    .reduce((map, key) => ({ ...map, [key]: fields[key] }), {});
+
+export const isEmptyObject = object => Object.keys(object).length === 0;
+
+export const databaseToForeignKeys = database =>
+  database && database.tables_lookup
+    ? Object.values(database.tables_lookup)
+        // ignore tables without primary key
+        .filter(
+          table =>
+            table && table.fields.find(field => isTypePK(field.semantic_type)),
+        )
+        .map(table => ({
+          table: table,
+          field:
+            table && table.fields.find(field => isTypePK(field.semantic_type)),
+        }))
+        .map(({ table, field }) => ({
+          id: field.id,
+          name:
+            table.schema_name && table.schema_name !== "public"
+              ? `${titleize(humanize(table.schema_name))}.${
+                  table.display_name
+                } → ${field.display_name}`
+              : `${table.display_name} → ${field.display_name}`,
+          description: field.description,
+        }))
+        .reduce((map, foreignKey) => assoc(map, foreignKey.id, foreignKey), {})
+    : {};
+
+// TODO Atte Keinänen 7/3/17: Construct question with Question of metabase-lib instead of this using function
+export const getQuestion = ({
+  dbId,
+  tableId,
+  fieldId,
+  metricId,
+  segmentId,
+  getCount,
+  visualization,
+  metadata,
+}) => {
+  const newQuestion = startNewCard("query", dbId, tableId);
+
+  // consider taking a look at Ramda as a possible underscore alternative?
+  // http://ramdajs.com/0.21.0/index.html
+  const question = chain(newQuestion)
+    .updateIn(["dataset_query", "query", "aggregation"], aggregation =>
+      getCount ? [["count"]] : aggregation,
+    )
+    .updateIn(["display"], display => visualization || display)
+    .updateIn(["dataset_query", "query", "breakout"], oldBreakout => {
+      if (fieldId && metadata && metadata.field(fieldId)) {
+        return [metadata.field(fieldId).getDefaultBreakout()];
+      }
+      if (fieldId) {
+        return [["field", fieldId, null]];
+      }
+      return oldBreakout;
+    })
+    .value();
+
+  if (metricId) {
+    return assocIn(
+      question,
+      ["dataset_query", "query", "aggregation"],
+      [["metric", metricId]],
+    );
+  }
+
+  if (segmentId) {
+    return assocIn(
+      question,
+      ["dataset_query", "query", "filter"],
+      ["segment", segmentId],
+    );
+  }
+
+  return question;
+};
+
+export const getQuestionUrl = getQuestionArgs =>
+  Urls.question(null, { hash: getQuestion(getQuestionArgs) });
+
+// little utility function to determine if we 'has' things, useful
+// for handling entity empty states
+export const has = entity => entity && entity.length > 0;
diff --git a/frontend/src/metabase/reference/utils.unit.spec.js b/frontend/src/metabase/reference/utils.unit.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..061773532e35168f649bf6d031c8e90723018003
--- /dev/null
+++ b/frontend/src/metabase/reference/utils.unit.spec.js
@@ -0,0 +1,275 @@
+import { databaseToForeignKeys, getQuestion } from "metabase/reference/utils";
+
+import { separateTablesBySchema } from "metabase/reference/databases/TableList";
+import { TYPE } from "metabase-lib/types/constants";
+
+describe("Reference utils.js", () => {
+  describe("databaseToForeignKeys()", () => {
+    it("should build foreignKey viewmodels from database", () => {
+      const database = {
+        tables_lookup: {
+          1: {
+            id: 1,
+            display_name: "foo",
+            schema_name: "PUBLIC",
+            fields: [
+              {
+                id: 1,
+                semantic_type: TYPE.PK,
+                display_name: "bar",
+                description: "foobar",
+              },
+            ],
+          },
+          2: {
+            id: 2,
+            display_name: "bar",
+            schema_name: "public",
+            fields: [
+              {
+                id: 2,
+                semantic_type: TYPE.PK,
+                display_name: "foo",
+                description: "barfoo",
+              },
+            ],
+          },
+          3: {
+            id: 3,
+            display_name: "boo",
+            schema_name: "TEST",
+            fields: [
+              {
+                id: 3,
+                display_name: "boo",
+                description: "booboo",
+              },
+            ],
+          },
+        },
+      };
+
+      const foreignKeys = databaseToForeignKeys(database);
+
+      expect(foreignKeys).toEqual({
+        1: { id: 1, name: "Public.foo → bar", description: "foobar" },
+        2: { id: 2, name: "bar → foo", description: "barfoo" },
+      });
+    });
+  });
+
+  describe("tablesToSchemaSeparatedTables()", () => {
+    it("should add schema separator to appropriate locations and sort tables by name", () => {
+      const tables = {
+        1: { id: 1, name: "Toucan", schema_name: "foo" },
+        2: { id: 2, name: "Elephant", schema_name: "bar" },
+        3: { id: 3, name: "Giraffe", schema_name: "boo" },
+        4: { id: 4, name: "Wombat", schema_name: "bar" },
+        5: { id: 5, name: "Anaconda", schema_name: "foo" },
+        6: { id: 6, name: "Buffalo", schema_name: "bar" },
+      };
+
+      const createSchemaSeparator = table => table.schema_name;
+      const createListItem = table => table;
+
+      const schemaSeparatedTables = separateTablesBySchema(
+        tables,
+        createSchemaSeparator,
+        createListItem,
+      );
+
+      expect(schemaSeparatedTables).toEqual([
+        ["bar", { id: 6, name: "Buffalo", schema_name: "bar" }],
+        { id: 2, name: "Elephant", schema_name: "bar" },
+        { id: 4, name: "Wombat", schema_name: "bar" },
+        ["boo", { id: 3, name: "Giraffe", schema_name: "boo" }],
+        ["foo", { id: 5, name: "Anaconda", schema_name: "foo" }],
+        { id: 1, name: "Toucan", schema_name: "foo" },
+      ]);
+    });
+  });
+
+  describe("getQuestion()", () => {
+    const getNewQuestion = ({
+      database = 1,
+      table = 2,
+      display = "table",
+      aggregation,
+      breakout,
+      filter,
+    }) => {
+      const card = {
+        name: null,
+        display: display,
+        visualization_settings: {},
+        dataset_query: {
+          database: database,
+          type: "query",
+          query: {
+            "source-table": table,
+          },
+        },
+      };
+      if (aggregation != null) {
+        card.dataset_query.query.aggregation = aggregation;
+      }
+      if (breakout != null) {
+        card.dataset_query.query.breakout = breakout;
+      }
+      if (filter != null) {
+        card.dataset_query.query.filter = filter;
+      }
+      return card;
+    };
+
+    it("should generate correct question for table raw data", () => {
+      const question = getQuestion({
+        dbId: 3,
+        tableId: 4,
+      });
+
+      expect(question).toEqual(
+        getNewQuestion({
+          database: 3,
+          table: 4,
+        }),
+      );
+    });
+
+    it("should generate correct question for table counts", () => {
+      const question = getQuestion({
+        dbId: 3,
+        tableId: 4,
+        getCount: true,
+      });
+
+      expect(question).toEqual(
+        getNewQuestion({
+          database: 3,
+          table: 4,
+          aggregation: [["count"]],
+        }),
+      );
+    });
+
+    it("should generate correct question for field raw data", () => {
+      const question = getQuestion({
+        dbId: 3,
+        tableId: 4,
+        fieldId: 5,
+      });
+
+      expect(question).toEqual(
+        getNewQuestion({
+          database: 3,
+          table: 4,
+          breakout: [["field", 5, null]],
+        }),
+      );
+    });
+
+    it("should generate correct question for field group by bar chart", () => {
+      const question = getQuestion({
+        dbId: 3,
+        tableId: 4,
+        fieldId: 5,
+        getCount: true,
+        visualization: "bar",
+      });
+
+      expect(question).toEqual(
+        getNewQuestion({
+          database: 3,
+          table: 4,
+          display: "bar",
+          breakout: [["field", 5, null]],
+          aggregation: [["count"]],
+        }),
+      );
+    });
+
+    it("should generate correct question for field group by pie chart", () => {
+      const question = getQuestion({
+        dbId: 3,
+        tableId: 4,
+        fieldId: 5,
+        getCount: true,
+        visualization: "pie",
+      });
+
+      expect(question).toEqual(
+        getNewQuestion({
+          database: 3,
+          table: 4,
+          display: "pie",
+          breakout: [["field", 5, null]],
+          aggregation: [["count"]],
+        }),
+      );
+    });
+
+    it("should generate correct question for metric raw data", () => {
+      const question = getQuestion({
+        dbId: 1,
+        tableId: 2,
+        metricId: 3,
+      });
+
+      expect(question).toEqual(
+        getNewQuestion({
+          aggregation: [["metric", 3]],
+        }),
+      );
+    });
+
+    it("should generate correct question for metric group by fields", () => {
+      const question = getQuestion({
+        dbId: 1,
+        tableId: 2,
+        fieldId: 4,
+        metricId: 3,
+      });
+
+      expect(question).toEqual(
+        getNewQuestion({
+          aggregation: [["metric", 3]],
+          breakout: [["field", 4, null]],
+        }),
+      );
+    });
+
+    it("should generate correct question for segment raw data", () => {
+      const question = getQuestion({
+        dbId: 2,
+        tableId: 3,
+        segmentId: 4,
+      });
+
+      expect(question).toEqual(
+        getNewQuestion({
+          database: 2,
+          table: 3,
+          filter: ["segment", 4],
+        }),
+      );
+    });
+
+    it("should generate correct question for segment counts", () => {
+      const question = getQuestion({
+        dbId: 2,
+        tableId: 3,
+        segmentId: 4,
+        getCount: true,
+      });
+
+      expect(question).toEqual(
+        getNewQuestion({
+          database: 2,
+          table: 3,
+          aggregation: [["count"]],
+          filter: ["segment", 4],
+        }),
+      );
+    });
+  });
+});
diff --git a/frontend/src/metabase/routes.jsx b/frontend/src/metabase/routes.jsx
index 41ea58f624b29412fe58511d446dc3279119d3e2..dad1385932ffa02cea6771b506badb8fcd3de7bc 100644
--- a/frontend/src/metabase/routes.jsx
+++ b/frontend/src/metabase/routes.jsx
@@ -44,6 +44,29 @@ import { UnsubscribePage } from "metabase/containers/Unsubscribe";
 import { Unauthorized } from "metabase/containers/ErrorPages";
 import NotFoundFallbackPage from "metabase/containers/NotFoundFallbackPage";
 
+// Reference Metrics
+import MetricListContainer from "metabase/reference/metrics/MetricListContainer";
+import MetricDetailContainer from "metabase/reference/metrics/MetricDetailContainer";
+import MetricQuestionsContainer from "metabase/reference/metrics/MetricQuestionsContainer";
+import MetricRevisionsContainer from "metabase/reference/metrics/MetricRevisionsContainer";
+
+// Reference Segments
+import SegmentListContainer from "metabase/reference/segments/SegmentListContainer";
+import SegmentDetailContainer from "metabase/reference/segments/SegmentDetailContainer";
+import SegmentQuestionsContainer from "metabase/reference/segments/SegmentQuestionsContainer";
+import SegmentRevisionsContainer from "metabase/reference/segments/SegmentRevisionsContainer";
+import SegmentFieldListContainer from "metabase/reference/segments/SegmentFieldListContainer";
+import SegmentFieldDetailContainer from "metabase/reference/segments/SegmentFieldDetailContainer";
+
+// Reference Databases
+import DatabaseListContainer from "metabase/reference/databases/DatabaseListContainer";
+import DatabaseDetailContainer from "metabase/reference/databases/DatabaseDetailContainer";
+import TableListContainer from "metabase/reference/databases/TableListContainer";
+import TableDetailContainer from "metabase/reference/databases/TableDetailContainer";
+import TableQuestionsContainer from "metabase/reference/databases/TableQuestionsContainer";
+import FieldListContainer from "metabase/reference/databases/FieldListContainer";
+import FieldDetailContainer from "metabase/reference/databases/FieldDetailContainer";
+
 import getAccountRoutes from "metabase/account/routes";
 import getAdminRoutes from "metabase/admin/routes";
 import getCollectionTimelineRoutes from "metabase/timelines/collections/routes";
@@ -197,6 +220,71 @@ export const getRoutes = store => (
 
         <Route path="/auto/dashboard/*" component={AutomaticDashboardApp} />
 
+        {/* REFERENCE */}
+        <Route path="/reference" title={t`Data Reference`}>
+          <IndexRedirect to="/reference/databases" />
+          <Route path="metrics" component={MetricListContainer} />
+          <Route path="metrics/:metricId" component={MetricDetailContainer} />
+          <Route
+            path="metrics/:metricId/edit"
+            component={MetricDetailContainer}
+          />
+          <Route
+            path="metrics/:metricId/questions"
+            component={MetricQuestionsContainer}
+          />
+          <Route
+            path="metrics/:metricId/revisions"
+            component={MetricRevisionsContainer}
+          />
+          <Route path="segments" component={SegmentListContainer} />
+          <Route
+            path="segments/:segmentId"
+            component={SegmentDetailContainer}
+          />
+          <Route
+            path="segments/:segmentId/fields"
+            component={SegmentFieldListContainer}
+          />
+          <Route
+            path="segments/:segmentId/fields/:fieldId"
+            component={SegmentFieldDetailContainer}
+          />
+          <Route
+            path="segments/:segmentId/questions"
+            component={SegmentQuestionsContainer}
+          />
+          <Route
+            path="segments/:segmentId/revisions"
+            component={SegmentRevisionsContainer}
+          />
+          <Route path="databases" component={DatabaseListContainer} />
+          <Route
+            path="databases/:databaseId"
+            component={DatabaseDetailContainer}
+          />
+          <Route
+            path="databases/:databaseId/tables"
+            component={TableListContainer}
+          />
+          <Route
+            path="databases/:databaseId/tables/:tableId"
+            component={TableDetailContainer}
+          />
+          <Route
+            path="databases/:databaseId/tables/:tableId/fields"
+            component={FieldListContainer}
+          />
+          <Route
+            path="databases/:databaseId/tables/:tableId/fields/:fieldId"
+            component={FieldDetailContainer}
+          />
+          <Route
+            path="databases/:databaseId/tables/:tableId/questions"
+            component={TableQuestionsContainer}
+          />
+        </Route>
+
         {/* PULSE */}
         <Route path="/pulse" title={t`Pulses`}>
           {/* NOTE: legacy route, not linked to in app */}
diff --git a/frontend/src/metabase/selectors/metadata.ts b/frontend/src/metabase/selectors/metadata.ts
index eea37392bf26258499908369fadc0ebf871095ac..2d8d47ef4cb12eb16f2203b6424d3478cc24d42d 100644
--- a/frontend/src/metabase/selectors/metadata.ts
+++ b/frontend/src/metabase/selectors/metadata.ts
@@ -89,6 +89,12 @@ const getNormalizedMetrics = (state: State) => state.entities.metrics;
 const getNormalizedSegments = (state: State) => state.entities.segments;
 const getNormalizedQuestions = (state: State) => state.entities.questions;
 
+export const getShallowDatabases = getNormalizedDatabases;
+export const getShallowTables = getNormalizedTables;
+export const getShallowFields = getNormalizedFields;
+export const getShallowMetrics = getNormalizedMetrics;
+export const getShallowSegments = getNormalizedSegments;
+
 export const getMetadata: (
   state: State,
   props?: MetadataSelectorOpts,
diff --git a/frontend/src/metabase/services.js b/frontend/src/metabase/services.js
index ef52ca115d83cc96e1c6b4840e0c70717a1734c0..030e2dcb4a62b15bbe941cf93c9634f0e74fc5d6 100644
--- a/frontend/src/metabase/services.js
+++ b/frontend/src/metabase/services.js
@@ -320,6 +320,7 @@ export const MetabaseApi = {
   db_get: GET("/api/database/:dbId"),
   db_update: PUT("/api/database/:id"),
   db_delete: DELETE("/api/database/:dbId"),
+  db_metadata: GET("/api/database/:dbId/metadata"),
   db_schemas: GET("/api/database/:dbId/schemas"),
   db_syncable_schemas: GET("/api/database/:dbId/syncable_schemas"),
   db_schema_tables: GET("/api/database/:dbId/schema/:schemaName"),