diff --git a/e2e/test/scenarios/admin/databases/default-sample-database.cy.spec.js b/e2e/test/scenarios/admin/databases/default-sample-database.cy.spec.js
index dc5b6797356b047d24cd5e9f1f7eee75dbe5fe97..65a2a904313a1728796bdf582f7a9b30f9f543a7 100644
--- a/e2e/test/scenarios/admin/databases/default-sample-database.cy.spec.js
+++ b/e2e/test/scenarios/admin/databases/default-sample-database.cy.spec.js
@@ -297,6 +297,7 @@ describe("scenarios > admin > databases > sample database", () => {
       cy.findByText("Browse data").click();
     });
 
+    cy.findByRole("tab", { name: "Databases" }).click();
     cy.findByTestId("database-browser").within(() => {
       cy.findByText("Sample Database").should("exist");
     });
diff --git a/e2e/test/scenarios/admin/datamodel/hide_tables.cy.spec.js b/e2e/test/scenarios/admin/datamodel/hide_tables.cy.spec.js
index 90316b02134595df695aced5f0232de851680a79..de108eae514651ca68baa6dd777915133fc4c77b 100644
--- a/e2e/test/scenarios/admin/datamodel/hide_tables.cy.spec.js
+++ b/e2e/test/scenarios/admin/datamodel/hide_tables.cy.spec.js
@@ -16,7 +16,7 @@ describe("scenarios > admin > datamodel > hidden tables (metabase#9759)", () =>
 
   it("hidden table should not show up in various places in UI", () => {
     // Visit the main page, we shouldn't be able to see the table
-    cy.visit(`/browse/${SAMPLE_DB_ID}`);
+    cy.visit(`/browse/databases/${SAMPLE_DB_ID}`);
     // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
     cy.contains("Products");
     // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
@@ -24,7 +24,7 @@ describe("scenarios > admin > datamodel > hidden tables (metabase#9759)", () =>
 
     // It shouldn't show up for a normal user either
     cy.signInAsNormalUser();
-    cy.visit(`/browse/${SAMPLE_DB_ID}`);
+    cy.visit(`/browse/databases/${SAMPLE_DB_ID}`);
     // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
     cy.contains("Products");
     // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
diff --git a/e2e/test/scenarios/embedding/embedding-full-app.cy.spec.js b/e2e/test/scenarios/embedding/embedding-full-app.cy.spec.js
index e227285553bf60b9f9a78feeba9dd97d2a849083..9e089cc4b67760f1b20967f2f71627ac0b61895c 100644
--- a/e2e/test/scenarios/embedding/embedding-full-app.cy.spec.js
+++ b/e2e/test/scenarios/embedding/embedding-full-app.cy.spec.js
@@ -158,8 +158,8 @@ describeEE("scenarios > embedding > full app", () => {
         url: "/browse",
         qs: { side_nav: false, logo: false },
       });
-      cy.findByRole("heading", { name: /Our data/ }).should("be.visible");
-      cy.findByRole("treeitem", { name: /Our data/ }).should("not.exist");
+      cy.findByRole("heading", { name: /Browse data/ }).should("be.visible");
+      cy.findByRole("treeitem", { name: /Browse data/ }).should("not.exist");
       cy.findByRole("treeitem", { name: "Our analytics" }).should("not.exist");
       appBar().should("not.exist");
     });
diff --git a/e2e/test/scenarios/onboarding/auth/signin.cy.spec.js b/e2e/test/scenarios/onboarding/auth/signin.cy.spec.js
index b8b9152a847a2b4e64ff764d6aea30f82ddf7a59..22bbcc1cd6ed228e85d1807002cccbe91cfe5d78 100644
--- a/e2e/test/scenarios/onboarding/auth/signin.cy.spec.js
+++ b/e2e/test/scenarios/onboarding/auth/signin.cy.spec.js
@@ -76,6 +76,7 @@ describe("scenarios > auth > signin", () => {
     cy.signInAsAdmin();
     cy.visit("/");
     browse().click();
+    cy.findByRole("tab", { name: "Databases" }).click();
     cy.findByRole("heading", { name: "Sample Database" }).click();
     cy.findByRole("heading", { name: "Orders" }).click();
     cy.wait("@dataset");
diff --git a/e2e/test/scenarios/onboarding/home/browse.cy.spec.js b/e2e/test/scenarios/onboarding/home/browse.cy.spec.js
index 7d71b03153e20147636389e92b4ef1eb3eee6f15..bf3648a05e008540bc9ac7fedf4a667db3d3ca31 100644
--- a/e2e/test/scenarios/onboarding/home/browse.cy.spec.js
+++ b/e2e/test/scenarios/onboarding/home/browse.cy.spec.js
@@ -6,22 +6,38 @@ describe("scenarios > browse data", () => {
     cy.signInAsAdmin();
   });
 
-  it("basic UI flow should work", () => {
+  it("can browse to a model", () => {
     cy.visit("/");
-    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-    cy.findByText(/Browse data/).click();
-    cy.location("pathname").should("eq", "/browse");
-    // 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.findByRole("listitem", { name: "Browse data" }).click();
+    cy.location("pathname").should("eq", "/browse/models");
+    cy.findByTestId("data-browser").findByText("Browse data");
+    cy.findByRole("heading", { name: "Orders Model" }).click();
+    cy.findByRole("button", { name: "Filter" });
+  });
+  it("can view summary of model's last edit", () => {
+    cy.visit("/");
+    cy.findByRole("listitem", { name: "Browse data" }).click();
+    cy.findByRole("note", /Bobby Tables.*7h./).realHover();
+    cy.findByRole("tooltip", { name: /Last edited by Bobby Tables/ });
+  });
+  it("can browse to a database", () => {
+    cy.visit("/");
+    cy.findByRole("listitem", { name: "Browse data" }).click();
+    cy.findByRole("tab", { name: "Databases" }).click();
+    cy.findByRole("heading", { name: "Sample Database" }).click();
+    cy.findByRole("heading", { name: "Products" }).click();
+    cy.findByRole("button", { name: "Summarize" });
+    cy.findByRole("link", { name: /Sample Database/ }).click();
+  });
+  it("can visit 'Learn about our data' page", () => {
+    cy.visit("/");
+    cy.findByRole("listitem", { name: "Browse data" }).click();
+    cy.findByRole("link", { name: /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();
-    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-    cy.findByText("Rustic Paper Wallet");
+    cy.findByRole("tab", { name: "Databases" }).click();
+    cy.findByRole("heading", { name: "Sample Database" }).click();
+    cy.findByRole("heading", { name: "Products" }).click();
+    cy.findByRole("gridcell", { name: "Rustic Paper Wallet" });
   });
 });
diff --git a/e2e/test/scenarios/onboarding/setup/setup.cy.spec.js b/e2e/test/scenarios/onboarding/setup/setup.cy.spec.js
index 2065089b62f10f01b0c52cfaaea48486b4bfe946..3e525c73124c7196dca5ba3b8d1db520dc350d9f 100644
--- a/e2e/test/scenarios/onboarding/setup/setup.cy.spec.js
+++ b/e2e/test/scenarios/onboarding/setup/setup.cy.spec.js
@@ -291,6 +291,7 @@ describe("scenarios > setup", () => {
     });
 
     cy.visit("/browse");
+    cy.findByRole("tab", { name: "Databases" }).click();
     cy.findByTestId("database-browser").findByText(dbName);
   });
 });
diff --git a/e2e/test/scenarios/onboarding/urls.cy.spec.js b/e2e/test/scenarios/onboarding/urls.cy.spec.js
index c6b361a9a13aa638090b413f1dc74363883a6ecb..e120354a759e1b625e486e7e81174d2c8dab10cf 100644
--- a/e2e/test/scenarios/onboarding/urls.cy.spec.js
+++ b/e2e/test/scenarios/onboarding/urls.cy.spec.js
@@ -24,20 +24,21 @@ describe("URLs", () => {
   });
 
   describe("browse databases", () => {
-    it(`should slugify database name when opening it from /browse"`, () => {
-      cy.visit("/browse");
+    it(`should slugify database name when opening it from /browse/databases"`, () => {
+      cy.visit("/browse/databases");
+      cy.findByRole("tab", { name: "Databases" }).click();
       cy.findByTextEnsureVisible("Sample Database").click();
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
       cy.findByText("Sample Database");
       cy.location("pathname").should(
         "eq",
-        `/browse/${SAMPLE_DB_ID}-sample-database`,
+        `/browse/databases/${SAMPLE_DB_ID}-sample-database`,
       );
     });
 
     [
-      `/browse/${SAVED_QUESTIONS_VIRTUAL_DB_ID}`,
-      `/browse/${SAVED_QUESTIONS_VIRTUAL_DB_ID}-saved-questions`,
+      `/browse/databases/${SAVED_QUESTIONS_VIRTUAL_DB_ID}`,
+      `/browse/databases/${SAVED_QUESTIONS_VIRTUAL_DB_ID}-saved-questions`,
     ].forEach(url => {
       it("should open 'Saved Questions' database correctly", () => {
         cy.visit(url);
diff --git a/e2e/test/scenarios/permissions/impersonated.cy.spec.js b/e2e/test/scenarios/permissions/impersonated.cy.spec.js
index 55164b3c6babe90583cdc678315182e984baf499..190ac11ab7984930acccffc8c29a33e358162080 100644
--- a/e2e/test/scenarios/permissions/impersonated.cy.spec.js
+++ b/e2e/test/scenarios/permissions/impersonated.cy.spec.js
@@ -329,7 +329,7 @@ describeEE("impersonated permission", () => {
     });
 
     it("have limited access", () => {
-      cy.visit(`/browse/${PG_DB_ID}`);
+      cy.visit(`/browse/databases/${PG_DB_ID}`);
 
       // No access through the visual query builder
       cy.get("main").within(() => {
@@ -340,7 +340,7 @@ describeEE("impersonated permission", () => {
       });
 
       // Has access to allowed tables
-      cy.visit(`/browse/${PG_DB_ID}`);
+      cy.visit(`/browse/databases/${PG_DB_ID}`);
 
       cy.get("main").findByText("Orders").click();
       cy.findAllByTestId("header-cell").contains("Subtotal");
diff --git a/e2e/test/scenarios/question/new.cy.spec.js b/e2e/test/scenarios/question/new.cy.spec.js
index b1a6b7e0f81d5d9aa0281e04c84a2f2cddb09ea2..fbe6efc6b1b2957851f6b07ff5dd49d11ad00913 100644
--- a/e2e/test/scenarios/question/new.cy.spec.js
+++ b/e2e/test/scenarios/question/new.cy.spec.js
@@ -74,15 +74,9 @@ describe("scenarios > question > new", () => {
 
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
       cy.contains("Our analytics");
-      // cy.findAllByRole("link", { name: "Our analytics" })
-      //   .should("have.attr", "href")
-      //   .and("eq", "/collection/root");
 
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
       cy.contains("Sample Database");
-      // cy.findAllByRole("link", { name: "Sample Database" })
-      //   .should("have.attr", "href")
-      //   .and("eq", `/browse/${SAMPLE_DB_ID}-sample-database`);
 
       // Discarding the search query should take us back to the original selector
       // that starts with the list of databases and saved questions
diff --git a/e2e/test/scenarios/question/settings.cy.spec.js b/e2e/test/scenarios/question/settings.cy.spec.js
index ff1ba407c502ea1d0d3d06b100c21d1b48fc2bd5..d8a15e23de0301d97798d52e4cfee1625b7c3ff4 100644
--- a/e2e/test/scenarios/question/settings.cy.spec.js
+++ b/e2e/test/scenarios/question/settings.cy.spec.js
@@ -469,6 +469,7 @@ describe("scenarios > question > settings", () => {
       // create a new question to see if the "add to a dashboard" modal is still there
       openNavigationSidebar();
       browse().click();
+      cy.findByRole("tab", { name: "Databases" }).click();
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
       cy.contains("Sample Database").click();
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
diff --git a/frontend/src/metabase-types/api/search.ts b/frontend/src/metabase-types/api/search.ts
index 7e5f9cdad887acd91effaace857e8f9935b455b1..98737145b7b136b90bc2441da79279df911d743a 100644
--- a/frontend/src/metabase-types/api/search.ts
+++ b/frontend/src/metabase-types/api/search.ts
@@ -48,6 +48,11 @@ export interface SearchResults {
   total: number;
 }
 
+export type CollectionEssentials = Pick<
+  Collection,
+  "id" | "name" | "authority_level"
+>;
+
 export interface SearchResult {
   id: number;
   name: string;
@@ -55,7 +60,7 @@ export interface SearchResult {
   description: string | null;
   archived: boolean | null;
   collection_position: number | null;
-  collection: Pick<Collection, "id" | "name" | "authority_level">;
+  collection: CollectionEssentials;
   table_id: TableId;
   bookmark: boolean | null;
   database_id: DatabaseId;
diff --git a/frontend/src/metabase/browse/components/BrowseApp.jsx b/frontend/src/metabase/browse/components/BrowseApp.jsx
deleted file mode 100644
index aff202d7f8a4e9dee11e35546cca8791891027fc..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/browse/components/BrowseApp.jsx
+++ /dev/null
@@ -1,6 +0,0 @@
-/* eslint-disable react/prop-types */
-import { BrowseAppRoot } from "./BrowseApp.styled";
-
-export default function BrowseApp({ children }) {
-  return <BrowseAppRoot data-testid="browse-data">{children}</BrowseAppRoot>;
-}
diff --git a/frontend/src/metabase/browse/components/BrowseApp.styled.tsx b/frontend/src/metabase/browse/components/BrowseApp.styled.tsx
index 89e95762902480627b5473c1b5ed2fcbd99b9f67..6c4dee19fbeaad93deabd5ccdb37f4cd67b4f106 100644
--- a/frontend/src/metabase/browse/components/BrowseApp.styled.tsx
+++ b/frontend/src/metabase/browse/components/BrowseApp.styled.tsx
@@ -1,17 +1,78 @@
 import styled from "@emotion/styled";
-import {
-  breakpointMinSmall,
-  breakpointMinMedium,
-} from "metabase/styled-components/theme";
+import { Tabs } from "metabase/ui";
+import { color } from "metabase/lib/colors";
+import EmptyState from "metabase/components/EmptyState";
 
 export const BrowseAppRoot = styled.div`
-  margin: 0 0.5rem;
+  flex: 1;
+  height: 100%;
+`;
 
-  ${breakpointMinSmall} {
-    margin: 0 1rem;
-  }
+export const BrowseTabs = styled(Tabs)`
+  display: flex;
+  flex-flow: column nowrap;
+  flex: 1;
+`;
+
+export const BrowseTabsList = styled(Tabs.List)`
+  padding: 0 1rem;
+  background-color: ${color("white")};
+  border-bottom-width: 1px;
+`;
 
-  ${breakpointMinMedium} {
-    margin: 0 4rem;
+export const BrowseTab = styled(Tabs.Tab)`
+  top: 1px;
+  margin-bottom: 1px;
+  border-bottom-width: 3px !important;
+  padding: 10px;
+  &:hover {
+    color: ${color("brand")};
+    background-color: inherit;
+    border-color: transparent;
   }
 `;
+
+export const BrowseTabsPanel = styled(Tabs.Panel)`
+  display: flex;
+  flex-flow: column nowrap;
+  flex: 1;
+  height: 100%;
+  padding: 0 1rem;
+`;
+
+export const BrowseContainer = styled.div`
+  display: flex;
+  flex: 1;
+  flex-flow: column nowrap;
+  height: 100%;
+`;
+
+export const BrowseDataHeader = styled.header`
+  display: flex;
+  padding: 1rem;
+  padding-bottom: 0.375rem;
+  color: ${color("dark")};
+  background-color: ${color("white")};
+`;
+
+export const BrowseSectionContainer = styled.div`
+  max-width: 1014px;
+  margin: 0 auto;
+  flex: 1;
+  display: flex;
+  width: 100%;
+`;
+
+export const BrowseTabsContainer = styled(BrowseSectionContainer)`
+  flex-flow: column nowrap;
+  justify-content: flex-start;
+`;
+
+export const CenteredEmptyState = styled(EmptyState)`
+  display: flex;
+  flex: 1;
+  flex-flow: column nowrap;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+`;
diff --git a/frontend/src/metabase/browse/components/BrowseApp.tsx b/frontend/src/metabase/browse/components/BrowseApp.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..def282c7f4cbe11b9cf7ae744e43b4235b62763d
--- /dev/null
+++ b/frontend/src/metabase/browse/components/BrowseApp.tsx
@@ -0,0 +1,113 @@
+import { t } from "ttag";
+import { push } from "react-router-redux";
+import { Icon, Text } from "metabase/ui";
+import {
+  useDatabaseListQuery,
+  useSearchListQuery,
+} from "metabase/common/hooks";
+import type { SearchResult } from "metabase-types/api";
+import { useDispatch } from "metabase/lib/redux";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+import Link from "metabase/core/components/Link";
+import { BrowseDatabases } from "./BrowseDatabases";
+import { BrowseModels } from "./BrowseModels";
+import {
+  BrowseAppRoot,
+  BrowseContainer,
+  BrowseDataHeader,
+  BrowseSectionContainer,
+  BrowseTab,
+  BrowseTabs,
+  BrowseTabsContainer,
+  BrowseTabsList,
+  BrowseTabsPanel,
+} from "./BrowseApp.styled";
+import { BrowseHeaderIconContainer } from "./BrowseHeader.styled";
+
+export type BrowseTabId = "models" | "databases";
+
+const isValidBrowseTab = (value: unknown): value is BrowseTabId =>
+  value === "models" || value === "databases";
+
+export const BrowseApp = ({
+  tab = "models",
+  children,
+}: {
+  tab?: string;
+  children?: React.ReactNode;
+}) => {
+  const dispatch = useDispatch();
+  const modelsResult = useSearchListQuery<SearchResult>({
+    query: {
+      models: ["dataset"],
+      filter_items_in_personal_collection: "exclude",
+    },
+  });
+  const databasesResult = useDatabaseListQuery();
+
+  if (!isValidBrowseTab(tab)) {
+    return <LoadingAndErrorWrapper error />;
+  }
+
+  return (
+    <BrowseAppRoot data-testid="browse-data">
+      <BrowseContainer data-testid="data-browser">
+        <BrowseDataHeader>
+          <BrowseSectionContainer>
+            <h2>{t`Browse data`}</h2>
+            <div
+              className="flex flex-align-right"
+              style={{ flexBasis: "40.0%" }}
+            >
+              <Link className="flex flex-align-right" to="reference">
+                <BrowseHeaderIconContainer>
+                  <Icon
+                    className="flex align-center"
+                    size={14}
+                    name="reference"
+                  />
+                  <Text
+                    size="md"
+                    lh="1"
+                    className="ml1 flex align-center text-bold"
+                  >
+                    {t`Learn about our data`}
+                  </Text>
+                </BrowseHeaderIconContainer>
+              </Link>
+            </div>
+          </BrowseSectionContainer>
+        </BrowseDataHeader>
+        <BrowseTabs
+          value={tab}
+          onTabChange={value => {
+            if (isValidBrowseTab(value)) {
+              dispatch(push(`/browse/${value}`));
+            }
+          }}
+        >
+          <BrowseTabsList>
+            <BrowseSectionContainer>
+              <BrowseTab key={"models"} value={"models"}>
+                {t`Models`}
+              </BrowseTab>
+              <BrowseTab key={"databases"} value={"databases"}>
+                {t`Databases`}
+              </BrowseTab>
+            </BrowseSectionContainer>
+          </BrowseTabsList>
+          <BrowseTabsPanel key={tab} value={tab}>
+            <BrowseTabsContainer>
+              {children ||
+                (tab === "models" ? (
+                  <BrowseModels modelsResult={modelsResult} />
+                ) : (
+                  <BrowseDatabases databasesResult={databasesResult} />
+                ))}
+            </BrowseTabsContainer>
+          </BrowseTabsPanel>
+        </BrowseTabs>
+      </BrowseContainer>
+    </BrowseAppRoot>
+  );
+};
diff --git a/frontend/src/metabase/browse/containers/DatabaseBrowser.styled.tsx b/frontend/src/metabase/browse/components/BrowseDatabases.styled.tsx
similarity index 75%
rename from frontend/src/metabase/browse/containers/DatabaseBrowser.styled.tsx
rename to frontend/src/metabase/browse/components/BrowseDatabases.styled.tsx
index 5e119e1af1f234843e4fa563bc5976e7a9166254..95cfcdf6fc6684fff2ecd0ad609a14622cf97f1e 100644
--- a/frontend/src/metabase/browse/containers/DatabaseBrowser.styled.tsx
+++ b/frontend/src/metabase/browse/components/BrowseDatabases.styled.tsx
@@ -5,10 +5,15 @@ import {
   breakpointMinSmall,
 } from "metabase/styled-components/theme";
 import Card from "metabase/components/Card";
-import { GridItem } from "metabase/components/Grid";
+import { GridItem, Grid } from "metabase/components/Grid";
+
+export const DatabaseGrid = styled(Grid)`
+  width: 100%;
+`;
 
 export const DatabaseCard = styled(Card)`
   padding: 1.5rem;
+  box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.06) !important;
 
   &:hover {
     color: ${color("brand")};
diff --git a/frontend/src/metabase/browse/components/BrowseDatabases.tsx b/frontend/src/metabase/browse/components/BrowseDatabases.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..79b9861bb639334d70ca66f0206ffcd39f7c04eb
--- /dev/null
+++ b/frontend/src/metabase/browse/components/BrowseDatabases.tsx
@@ -0,0 +1,62 @@
+import _ from "underscore";
+import { t } from "ttag";
+
+import * as Urls from "metabase/lib/urls";
+import { color } from "metabase/lib/colors";
+
+import { Icon, Box } from "metabase/ui";
+import Link from "metabase/core/components/Link";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import type { useDatabaseListQuery } from "metabase/common/hooks";
+
+import NoResults from "assets/img/no_results.svg";
+import {
+  DatabaseCard,
+  DatabaseGrid,
+  DatabaseGridItem,
+} from "./BrowseDatabases.styled";
+import { CenteredEmptyState } from "./BrowseApp.styled";
+
+export const BrowseDatabases = ({
+  databasesResult,
+}: {
+  databasesResult: ReturnType<typeof useDatabaseListQuery>;
+}) => {
+  const { data: databases = [], error, isLoading } = databasesResult;
+  if (error) {
+    return <LoadingAndErrorWrapper error />;
+  }
+  if (isLoading) {
+    return <LoadingAndErrorWrapper loading />;
+  }
+
+  return databases.length ? (
+    <DatabaseGrid data-testid="database-browser">
+      {databases.map(database => (
+        <DatabaseGridItem key={database.id}>
+          <Link to={Urls.browseDatabase(database)}>
+            <DatabaseCard>
+              <Icon
+                name="database"
+                color={color("accent2")}
+                className="mb3"
+                size={32}
+              />
+              <h3 className="text-wrap">{database.name}</h3>
+            </DatabaseCard>
+          </Link>
+        </DatabaseGridItem>
+      ))}
+    </DatabaseGrid>
+  ) : (
+    <CenteredEmptyState
+      title={<Box mb=".5rem">{t`No databases here yet`}</Box>}
+      illustrationElement={
+        <Box mb=".5rem">
+          <img src={NoResults} />
+        </Box>
+      }
+    />
+  );
+};
diff --git a/frontend/src/metabase/browse/components/BrowseDatabases.unit.spec.tsx b/frontend/src/metabase/browse/components/BrowseDatabases.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0dba32b0efa82e57cb04a9e71b3b7cb8c1f4a2ec
--- /dev/null
+++ b/frontend/src/metabase/browse/components/BrowseDatabases.unit.spec.tsx
@@ -0,0 +1,36 @@
+import { createMockDatabase } from "metabase-types/api/mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import type Database from "metabase-lib/metadata/Database";
+import { BrowseDatabases } from "./BrowseDatabases";
+
+const renderBrowseDatabases = (modelCount: number) => {
+  const databases = mockDatabases.slice(0, modelCount);
+  return renderWithProviders(
+    <BrowseDatabases
+      databasesResult={{ data: databases, isLoading: false, error: false }}
+    />,
+  );
+};
+
+const mockDatabases = [...Array(100)].map(
+  (_, index) =>
+    createMockDatabase({ id: index, name: `Database ${index}` }) as Database,
+);
+
+describe("BrowseDatabases", () => {
+  afterEach(() => {
+    jest.restoreAllMocks();
+  });
+  it("displays databases", async () => {
+    renderBrowseDatabases(10);
+    for (let i = 0; i < 10; i++) {
+      expect(await screen.findByText(`Database ${i}`)).toBeInTheDocument();
+    }
+  });
+  it("displays a 'no databases' message in the Databases tab when no databases exist", async () => {
+    renderBrowseDatabases(0);
+    expect(
+      await screen.findByText("No databases here yet"),
+    ).toBeInTheDocument();
+  });
+});
diff --git a/frontend/src/metabase/browse/components/BrowseHeader.styled.tsx b/frontend/src/metabase/browse/components/BrowseHeader.styled.tsx
index ada2adc0a917f796b0caeee583351a13737b23aa..f1ae9bb0e7ab4a8943271e3157bc7e586a671ebb 100644
--- a/frontend/src/metabase/browse/components/BrowseHeader.styled.tsx
+++ b/frontend/src/metabase/browse/components/BrowseHeader.styled.tsx
@@ -1,18 +1,13 @@
 import styled from "@emotion/styled";
 import { color } from "metabase/lib/colors";
 
-export const BrowseHeaderRoot = styled.div`
-  margin-top: 2rem;
-  margin-bottom: 1rem;
-`;
-
 export const BrowseHeaderContent = styled.div`
   display: flex;
   align-items: center;
-  margin-top: 0.5rem;
+  padding: 1rem 0.5rem 0.5rem 0.5rem;
 `;
 
-export const BrowserHeaderIconContainer = styled.div`
+export const BrowseHeaderIconContainer = styled.div`
   display: flex;
   align-items: center;
   color: ${color("text-medium")};
diff --git a/frontend/src/metabase/browse/components/BrowseHeader.tsx b/frontend/src/metabase/browse/components/BrowseHeader.tsx
deleted file mode 100644
index ae00793b30c4428aa2172a8e8ad43583ba9aa956..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/browse/components/BrowseHeader.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { t } from "ttag";
-import BrowserCrumbs from "metabase/components/BrowserCrumbs";
-
-import Link from "metabase/core/components/Link";
-import { Icon } from "metabase/ui";
-import {
-  BrowseHeaderContent,
-  BrowseHeaderRoot,
-  BrowserHeaderIconContainer,
-} from "./BrowseHeader.styled";
-
-type Crumb = { to?: string; title?: string };
-
-export const BrowseHeader = ({ crumbs = [] }: { crumbs: Crumb[] }) => {
-  return (
-    <BrowseHeaderRoot>
-      <BrowseHeaderContent>
-        <BrowserCrumbs crumbs={crumbs} />
-        <div className="flex flex-align-right">
-          <Link className="flex flex-align-right" to="reference">
-            <BrowserHeaderIconContainer>
-              <Icon className="flex align-center" size={14} name="reference" />
-              <span className="ml1 flex align-center text-bold">
-                {t`Learn about our data`}
-              </span>
-            </BrowserHeaderIconContainer>
-          </Link>
-        </div>
-      </BrowseHeaderContent>
-    </BrowseHeaderRoot>
-  );
-};
diff --git a/frontend/src/metabase/browse/components/BrowseModels.styled.tsx b/frontend/src/metabase/browse/components/BrowseModels.styled.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b30702ee7fa1206f7742c38120df196d089ef786
--- /dev/null
+++ b/frontend/src/metabase/browse/components/BrowseModels.styled.tsx
@@ -0,0 +1,84 @@
+import styled from "@emotion/styled";
+import { color } from "metabase/lib/colors";
+import {
+  breakpointMinMedium,
+  breakpointMinSmall,
+} from "metabase/styled-components/theme";
+import Card from "metabase/components/Card";
+import { Ellipsified } from "metabase/core/components/Ellipsified";
+import Link from "metabase/core/components/Link";
+import { Group } from "metabase/ui";
+
+export const ModelCard = styled(Card)`
+  padding: 1.5rem;
+  padding-bottom: 1rem;
+
+  height: 9rem;
+  display: flex;
+  flex-flow: column nowrap;
+  justify-content: flex-start;
+  align-items: flex-start;
+
+  border: 1px solid ${color("border")};
+  box-shadow: 0 1px 0.25rem 0 rgba(0, 0, 0, 0.06);
+  &:hover {
+    box-shadow: 0 1px 0.25rem 0 rgba(0, 0, 0, 0.14);
+    h4 {
+      color: ${color("brand")};
+    }
+  }
+  transition: box-shadow 0.15s;
+  h4 {
+    transition: color 0.15s;
+  }
+`;
+
+export const MultilineEllipsified = styled(Ellipsified)`
+  white-space: pre-line;
+  overflow: hidden;
+  text-overflow: ellipsis;
+
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+
+  // Without the following rule, the useIsTruncated hook,
+  // which Ellipsified calls, might think that this element
+  // is truncated when it is not
+  padding-bottom: 1px;
+`;
+
+export const GridContainer = styled.div`
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+  gap: 1rem;
+  width: 100%;
+
+  ${breakpointMinSmall} {
+    padding-bottom: 1rem;
+  }
+  ${breakpointMinMedium} {
+    padding-bottom: 3rem;
+  }
+`;
+
+export const CollectionHeaderContainer = styled.div`
+  grid-column: 1 / -1;
+  align-items: center;
+  padding-top: 0.5rem;
+  margin-right: 1rem;
+  &:not(:first-of-type) {
+    border-top: 1px solid #f0f0f0;
+  }
+`;
+
+export const CollectionHeaderLink = styled(Link)`
+  &:hover * {
+    color: ${color("brand")};
+  }
+`;
+
+export const CollectionHeaderGroup = styled(Group)`
+  position: relative;
+  top: 0.5rem;
+`;
diff --git a/frontend/src/metabase/browse/components/BrowseModels.tsx b/frontend/src/metabase/browse/components/BrowseModels.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..563cf5664dbf4fabd9d5f8bc0983eddffa517bdd
--- /dev/null
+++ b/frontend/src/metabase/browse/components/BrowseModels.tsx
@@ -0,0 +1,191 @@
+import _ from "underscore";
+import cx from "classnames";
+import { c, t } from "ttag";
+
+import type {
+  Card,
+  CollectionEssentials,
+  SearchResult,
+} from "metabase-types/api";
+import * as Urls from "metabase/lib/urls";
+
+import Link from "metabase/core/components/Link";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import type { useSearchListQuery } from "metabase/common/hooks";
+
+import { Box, Group, Icon, Text, Title } from "metabase/ui";
+import NoResults from "assets/img/no_results.svg";
+import { useSelector } from "metabase/lib/redux";
+import { getLocale } from "metabase/setup/selectors";
+import { isInstanceAnalyticsCollection } from "metabase/collections/utils";
+import { getCollectionName, groupModels } from "../utils";
+import { CenteredEmptyState } from "./BrowseApp.styled";
+import {
+  CollectionHeaderContainer,
+  CollectionHeaderGroup,
+  CollectionHeaderLink,
+  GridContainer,
+  ModelCard,
+  MultilineEllipsified,
+} from "./BrowseModels.styled";
+import { LastEdited } from "./LastEdited";
+
+export const BrowseModels = ({
+  modelsResult,
+}: {
+  modelsResult: ReturnType<typeof useSearchListQuery<SearchResult>>;
+}) => {
+  const { data: models = [], error, isLoading } = modelsResult;
+  const locale = useSelector(getLocale);
+  const localeCode: string | undefined = locale?.code;
+  const modelsFiltered = models.filter(
+    model => !isInstanceAnalyticsCollection(model.collection),
+  );
+  const groupsOfModels = groupModels(modelsFiltered, localeCode);
+
+  if (error || isLoading) {
+    return (
+      <LoadingAndErrorWrapper
+        error={error}
+        loading={isLoading}
+        style={{ display: "flex", flex: 1 }}
+      />
+    );
+  }
+
+  if (modelsFiltered.length) {
+    return (
+      <GridContainer role="grid">
+        {groupsOfModels.map(groupOfModels => (
+          <ModelGroup
+            models={groupOfModels}
+            key={`modelgroup-${groupOfModels[0].collection.id}`}
+            localeCode={localeCode}
+          />
+        ))}
+      </GridContainer>
+    );
+  }
+
+  return (
+    <CenteredEmptyState
+      title={<Box mb=".5rem">{t`No models here yet`}</Box>}
+      message={
+        <Box maw="24rem">{t`Models help curate data to make it easier to find answers to questions all in one place.`}</Box>
+      }
+      illustrationElement={
+        <Box mb=".5rem">
+          <img src={NoResults} />
+        </Box>
+      }
+    />
+  );
+};
+
+const ModelGroup = ({
+  models,
+  localeCode,
+}: {
+  models: SearchResult[];
+  localeCode: string | undefined;
+}) => {
+  const sortedModels = models.sort((a, b) => {
+    if (!a.name && b.name) {
+      return 1;
+    }
+    if (a.name && !b.name) {
+      return -1;
+    }
+    if (!a.name && !b.name) {
+      return 0;
+    }
+    const nameA = a.name.toLowerCase();
+    const nameB = b.name.toLowerCase();
+    return nameA.localeCompare(nameB, localeCode);
+  });
+  const collection = models[0].collection;
+
+  /** This id is used by aria-labelledby */
+  const collectionHtmlId = `collection-${collection.id}`;
+
+  // TODO: Check padding above the collection header
+  return (
+    <>
+      <CollectionHeader
+        collection={collection}
+        key={collectionHtmlId}
+        id={collectionHtmlId}
+      />
+      {sortedModels.map(model => (
+        <ModelCell
+          model={model}
+          collectionHtmlId={collectionHtmlId}
+          key={`model-${model.id}`}
+        />
+      ))}
+    </>
+  );
+};
+
+interface ModelCellProps {
+  model: SearchResult;
+  collectionHtmlId: string;
+}
+
+const ModelCell = ({ model, collectionHtmlId }: ModelCellProps) => {
+  const headingId = `heading-for-model-${model.id}`;
+
+  const lastEditorFullName =
+    model.last_editor_common_name ?? model.creator_common_name;
+  const timestamp = model.last_edited_at ?? model.created_at ?? "";
+
+  const noDescription = c(
+    "Indicates that a model has no description associated with it",
+  ).t`No description.`;
+  return (
+    <Link
+      aria-labelledby={`${collectionHtmlId} ${headingId}`}
+      key={model.id}
+      to={Urls.model(model as unknown as Partial<Card>)}
+    >
+      <ModelCard>
+        <Title order={4} className="text-wrap" lh="1rem" mb=".5rem">
+          <MultilineEllipsified tooltipMaxWidth="20rem" id={headingId}>
+            {model.name}
+          </MultilineEllipsified>
+        </Title>
+        <Text h="2rem" size="xs" mb="auto">
+          <MultilineEllipsified
+            tooltipMaxWidth="20rem"
+            className={cx({ "text-light": !model.description })}
+          >
+            {model.description || noDescription}{" "}
+          </MultilineEllipsified>
+        </Text>
+        <LastEdited editorFullName={lastEditorFullName} timestamp={timestamp} />
+      </ModelCard>
+    </Link>
+  );
+};
+
+const CollectionHeader = ({
+  collection,
+  id,
+}: {
+  collection: CollectionEssentials;
+  id: string;
+}) => {
+  return (
+    <CollectionHeaderContainer id={id} role="heading">
+      <CollectionHeaderGroup grow noWrap>
+        <CollectionHeaderLink to={Urls.collection(collection)}>
+          <Group spacing=".25rem">
+            <Icon name="folder" color="text-dark" size={16} />
+            <Text weight="bold">{getCollectionName(collection)}</Text>
+          </Group>
+        </CollectionHeaderLink>
+      </CollectionHeaderGroup>
+    </CollectionHeaderContainer>
+  );
+};
diff --git a/frontend/src/metabase/browse/components/BrowseModels.unit.spec.tsx b/frontend/src/metabase/browse/components/BrowseModels.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2f83d21fb5e6ebd19c1781539b4ce9131aabd841
--- /dev/null
+++ b/frontend/src/metabase/browse/components/BrowseModels.unit.spec.tsx
@@ -0,0 +1,294 @@
+import { renderWithProviders, screen, within } from "__support__/ui";
+import type { SearchResult } from "metabase-types/api";
+import { createMockSetupState } from "metabase-types/store/mocks";
+import {
+  createMockCollection,
+  createMockSearchResult,
+} from "metabase-types/api/mocks";
+import { defaultRootCollection } from "metabase/admin/permissions/pages/CollectionPermissionsPage/tests/setup";
+import { groupModels } from "../utils";
+import { BrowseModels } from "./BrowseModels";
+
+const renderBrowseModels = (modelCount: number) => {
+  const models = mockModels.slice(0, modelCount);
+  return renderWithProviders(
+    <BrowseModels
+      modelsResult={{ data: models, isLoading: false, error: false }}
+    />,
+    {
+      storeInitialState: {
+        setup: createMockSetupState({
+          locale: { name: "English", code: "en" },
+        }),
+      },
+    },
+  );
+};
+
+const collectionAlpha = createMockCollection({ id: 0, name: "Alpha" });
+const collectionBeta = createMockCollection({ id: 1, name: "Beta" });
+const collectionCharlie = createMockCollection({ id: 2, name: "Charlie" });
+const collectionDelta = createMockCollection({ id: 3, name: "Delta" });
+const collectionZulu = createMockCollection({ id: 4, name: "Zulu" });
+const collectionAngstrom = createMockCollection({ id: 5, name: "Ångström" });
+const collectionOzgur = createMockCollection({ id: 6, name: "Özgür" });
+
+const mockModels: SearchResult[] = [
+  {
+    id: 0,
+    name: "Model 0",
+    collection: collectionAlpha,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2024-12-15T11:59:59.000Z",
+  },
+  {
+    id: 1,
+    name: "Model 1",
+    collection: collectionAlpha,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2024-12-15T11:59:30.000Z",
+  },
+  {
+    id: 2,
+    name: "Model 2",
+    collection: collectionAlpha,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2024-12-15T11:59:00.000Z",
+  },
+  {
+    id: 3,
+    name: "Model 3",
+    collection: collectionBeta,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2024-12-15T11:50:00.000Z",
+  },
+  {
+    id: 4,
+    name: "Model 4",
+    collection: collectionBeta,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2024-12-15T11:00:00.000Z",
+  },
+  {
+    id: 5,
+    name: "Model 5",
+    collection: collectionBeta,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2024-12-14T22:00:00.000Z",
+  },
+  {
+    id: 6,
+    name: "Model 6",
+    collection: collectionCharlie,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2024-12-14T12:00:00.000Z",
+  },
+  {
+    id: 7,
+    name: "Model 7",
+    collection: collectionCharlie,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2024-12-10T12:00:00.000Z",
+  },
+  {
+    id: 8,
+    name: "Model 8",
+    collection: collectionCharlie,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2024-11-15T12:00:00.000Z",
+  },
+  {
+    id: 9,
+    name: "Model 9",
+    collection: collectionDelta,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2024-02-15T12:00:00.000Z",
+  },
+  {
+    id: 10,
+    name: "Model 10",
+    collection: collectionDelta,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2023-12-15T12:00:00.000Z",
+  },
+  {
+    id: 11,
+    name: "Model 11",
+    collection: collectionDelta,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2020-01-01T00:00:00.000Z",
+  },
+  {
+    id: 12,
+    name: "Model 12",
+    collection: collectionZulu,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2000-01-01T00:00:00.000Z",
+  },
+  {
+    id: 13,
+    name: "Model 13",
+    collection: collectionZulu,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2000-01-01T00:00:00.000Z",
+  },
+  {
+    id: 14,
+    name: "Model 14",
+    collection: collectionZulu,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2000-01-01T00:00:00.000Z",
+  },
+  {
+    id: 15,
+    name: "Model 15",
+    collection: collectionAngstrom,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2000-01-01T00:00:00.000Z",
+  },
+  {
+    id: 16,
+    name: "Model 16",
+    collection: collectionAngstrom,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2000-01-01T00:00:00.000Z",
+  },
+  {
+    id: 17,
+    name: "Model 17",
+    collection: collectionAngstrom,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2000-01-01T00:00:00.000Z",
+  },
+  {
+    id: 18,
+    name: "Model 18",
+    collection: collectionOzgur,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2000-01-01T00:00:00.000Z",
+  },
+  {
+    id: 19,
+    name: "Model 19",
+    collection: collectionOzgur,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2000-01-01T00:00:00.000Z",
+  },
+  {
+    id: 20,
+    name: "Model 20",
+    collection: collectionOzgur,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2000-01-01T00:00:00.000Z",
+  },
+  {
+    id: 21,
+    name: "Model 20",
+    collection: defaultRootCollection,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2000-01-01T00:00:00.000Z",
+  },
+  {
+    id: 22,
+    name: "Model 21",
+    collection: defaultRootCollection,
+    last_editor_common_name: "Bobby",
+    last_edited_at: "2000-01-01T00:00:00.000Z",
+  },
+].map(model => createMockSearchResult(model));
+
+describe("BrowseModels", () => {
+  it("displays models", async () => {
+    renderBrowseModels(10);
+    for (let i = 0; i < 10; i++) {
+      expect(await screen.findByText(`Model ${i}`)).toBeInTheDocument();
+    }
+  });
+  it("displays a 'no models' message in the Models tab when no models exist", async () => {
+    renderBrowseModels(0);
+    expect(await screen.findByText("No models here yet")).toBeInTheDocument();
+  });
+  it("displays models, organized by parent collection", async () => {
+    renderBrowseModels(10);
+    // Three <a> tags representing models have aria-labelledby="collection-1 model-$id",
+    // and "collection-1" is the id of an element containing text 'Collection 1',
+    // so the following line finds those <a> tags.
+    const modelsInCollection1 = await screen.findAllByLabelText("Alpha");
+    expect(modelsInCollection1).toHaveLength(3);
+    const modelsInCollection2 = await screen.findAllByLabelText("Beta");
+    expect(modelsInCollection2).toHaveLength(3);
+  });
+  it("displays the Our Analytics collection if it has a model", async () => {
+    renderBrowseModels(23);
+    const modelsInOurAnalytics = await screen.findAllByLabelText(
+      "Our analytics",
+    );
+    expect(modelsInOurAnalytics).toHaveLength(2);
+  });
+  it("displays last edited information about models", async () => {
+    jest.useFakeTimers().setSystemTime(new Date("2024-12-15T12:00:00.000Z"));
+
+    renderBrowseModels(12);
+    const howLongAgo = /\d+(min|h|d|mo|yr)/;
+    const findWhenModelWasEdited = async (modelName: string) =>
+      (
+        await within(await screen.findByLabelText(modelName)).findByText(
+          howLongAgo,
+        )
+      )?.textContent?.match(howLongAgo)?.[0];
+
+    expect(await findWhenModelWasEdited("Model 0")).toBe("1min");
+    expect(await findWhenModelWasEdited("Model 1")).toBe("1min");
+    expect(await findWhenModelWasEdited("Model 2")).toBe("1min");
+    expect(await findWhenModelWasEdited("Model 3")).toBe("10min");
+    expect(await findWhenModelWasEdited("Model 4")).toBe("1h");
+    expect(await findWhenModelWasEdited("Model 5")).toBe("14h");
+    expect(await findWhenModelWasEdited("Model 6")).toBe("1d");
+    expect(await findWhenModelWasEdited("Model 7")).toBe("5d");
+    expect(await findWhenModelWasEdited("Model 8")).toBe("1mo");
+    expect(await findWhenModelWasEdited("Model 9")).toBe("10mo");
+    expect(await findWhenModelWasEdited("Model 10")).toBe("1yr");
+    expect(await findWhenModelWasEdited("Model 11")).toBe("5yr");
+
+    jest.useRealTimers();
+  });
+  it("has a function that groups models by collection, sorting the collections alphabetically when English is the locale", () => {
+    const groupedModels = groupModels(mockModels, "en-US");
+    expect(groupedModels[0][0].collection.name).toEqual("Alpha");
+    expect(groupedModels[0]).toHaveLength(3);
+    expect(groupedModels[1][0].collection.name).toEqual("Ångström");
+    expect(groupedModels[1]).toHaveLength(3);
+    expect(groupedModels[2][0].collection.name).toEqual("Beta");
+    expect(groupedModels[2]).toHaveLength(3);
+    expect(groupedModels[3][0].collection.name).toEqual("Charlie");
+    expect(groupedModels[3]).toHaveLength(3);
+    expect(groupedModels[4][0].collection.name).toEqual("Delta");
+    expect(groupedModels[4]).toHaveLength(3);
+    expect(groupedModels[5][0].collection.name).toEqual("Our analytics");
+    expect(groupedModels[5]).toHaveLength(2);
+    expect(groupedModels[6][0].collection.name).toEqual("Özgür");
+    expect(groupedModels[6]).toHaveLength(3);
+    expect(groupedModels[7][0].collection.name).toEqual("Zulu");
+    expect(groupedModels[7]).toHaveLength(3);
+  });
+
+  it("has a function that groups models by collection, sorting the collections alphabetically when Swedish is the locale", () => {
+    const groupedModels = groupModels(mockModels, "sv-SV");
+    expect(groupedModels[0][0].collection.name).toEqual("Alpha");
+    expect(groupedModels[0]).toHaveLength(3);
+    expect(groupedModels[1][0].collection.name).toEqual("Beta");
+    expect(groupedModels[1]).toHaveLength(3);
+    expect(groupedModels[2][0].collection.name).toEqual("Charlie");
+    expect(groupedModels[2]).toHaveLength(3);
+    expect(groupedModels[3][0].collection.name).toEqual("Delta");
+    expect(groupedModels[3]).toHaveLength(3);
+    expect(groupedModels[4][0].collection.name).toEqual("Our analytics");
+    expect(groupedModels[4]).toHaveLength(2);
+    expect(groupedModels[5][0].collection.name).toEqual("Zulu");
+    expect(groupedModels[5]).toHaveLength(3);
+    expect(groupedModels[6][0].collection.name).toEqual("Ångström");
+    expect(groupedModels[6]).toHaveLength(3);
+    expect(groupedModels[7][0].collection.name).toEqual("Özgür");
+    expect(groupedModels[7]).toHaveLength(3);
+  });
+});
diff --git a/frontend/src/metabase/browse/components/LastEdited.tsx b/frontend/src/metabase/browse/components/LastEdited.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3ba973bcadcad53a4d041334f52be831f6a3c1b3
--- /dev/null
+++ b/frontend/src/metabase/browse/components/LastEdited.tsx
@@ -0,0 +1,80 @@
+import _ from "underscore";
+import { c, t } from "ttag";
+import dayjs from "dayjs";
+import relativeTime from "dayjs/plugin/relativeTime";
+import updateLocale from "dayjs/plugin/updateLocale";
+
+import { Text, Tooltip } from "metabase/ui";
+import { formatDateTimeWithUnit } from "metabase/lib/formatting";
+
+dayjs.extend(updateLocale);
+dayjs.extend(relativeTime);
+
+const timeFormattingRules: Record<string, unknown> = {
+  m: t`${1}min`,
+  mm: t`${"%d"}min`,
+  h: t`${1}h`,
+  hh: t`${"%d"}h`,
+  d: t`${1}d`,
+  dd: t`${"%d"}d`,
+  M: t`${1}mo`,
+  MM: t`${"%d"}mo`,
+  y: t`${1}yr`,
+  yy: t`${"%d"}yr`,
+  // Display any number of seconds as "1min"
+  s: () => t`${1}min`,
+  ss: () => t`${1}min`,
+  // Don't use "ago"
+  past: "%s",
+  // For the edge case where a model's last-edit date is somehow in the future
+  future: t`${"%s"} from now`,
+};
+
+const getTimePassedSince = (timestamp: string) => {
+  const date = dayjs(timestamp);
+  if (timestamp && date.isValid()) {
+    const locale = dayjs.locale();
+    const cachedRules = dayjs.Ls[locale].relativeTime;
+    dayjs.updateLocale(locale, { relativeTime: timeFormattingRules });
+    const timePassed = date.fromNow();
+    dayjs.updateLocale(locale, { relativeTime: cachedRules });
+    return timePassed;
+  } else {
+    return t`(invalid date)`;
+  }
+};
+
+export const LastEdited = ({
+  editorFullName,
+  timestamp,
+}: {
+  editorFullName: string | null;
+  timestamp: string;
+}) => {
+  const timePassed = getTimePassedSince(timestamp);
+  const timeLabel = timestamp ? timePassed : "";
+  const formattedDate = formatDateTimeWithUnit(timestamp, "day", {});
+  const time = (
+    <time key="time" dateTime={timestamp}>
+      {formattedDate}
+    </time>
+  );
+
+  const tooltipLabel = c(
+    "{0} is the full name (or if this is unavailable, the email address) of the last person who edited a model. {1} is a date",
+  ).jt`Last edited by ${editorFullName}${(<br key="br" />)}${time}`;
+
+  return (
+    <Tooltip label={tooltipLabel} withArrow disabled={!timeLabel}>
+      <Text role="note" size="small">
+        {editorFullName}
+        {editorFullName && timePassed && (
+          <Text span px=".33rem" color="text-light">
+            •
+          </Text>
+        )}
+        {timePassed}
+      </Text>
+    </Tooltip>
+  );
+};
diff --git a/frontend/src/metabase/browse/containers/SchemaBrowser.jsx b/frontend/src/metabase/browse/components/SchemaBrowser.jsx
similarity index 73%
rename from frontend/src/metabase/browse/containers/SchemaBrowser.jsx
rename to frontend/src/metabase/browse/components/SchemaBrowser.jsx
index acc682bcfb9fd147eed7d3e519ce3619fdb660c5..b19a3426bc96ce0b86a2e24d8939dd4594679bdc 100644
--- a/frontend/src/metabase/browse/containers/SchemaBrowser.jsx
+++ b/frontend/src/metabase/browse/components/SchemaBrowser.jsx
@@ -12,15 +12,20 @@ import TableBrowser from "metabase/browse/containers/TableBrowser";
 import * as Urls from "metabase/lib/urls";
 import { color } from "metabase/lib/colors";
 
-import { BrowseHeader } from "metabase/browse/components/BrowseHeader";
-import { SchemaGridItem, SchemaLink } from "./SchemaBrowser.styled";
+import BrowserCrumbs from "metabase/components/BrowserCrumbs";
+import {
+  SchemaBrowserContainer,
+  SchemaGridItem,
+  SchemaLink,
+} from "./SchemaBrowser.styled";
+import { BrowseHeaderContent } from "./BrowseHeader.styled";
 
 function SchemaBrowser(props) {
   const { schemas, params } = props;
   const { slug } = params;
   const dbId = Urls.extractEntityId(slug);
   return (
-    <div>
+    <SchemaBrowserContainer>
       {schemas.length === 1 ? (
         <TableBrowser
           {...props}
@@ -30,13 +35,15 @@ function SchemaBrowser(props) {
           showSchemaInHeader={false}
         />
       ) : (
-        <div>
-          <BrowseHeader
-            crumbs={[
-              { title: t`Our data`, to: "browse" },
-              { title: <Database.Name id={dbId} /> },
-            ]}
-          />
+        <>
+          <BrowseHeaderContent>
+            <BrowserCrumbs
+              crumbs={[
+                { title: t`Databases`, to: "/browse/databases" },
+                { title: <Database.Name id={dbId} /> },
+              ]}
+            />
+          </BrowseHeaderContent>
           {schemas.length === 0 ? (
             <h2 className="full text-centered text-medium">{t`This database doesn't have any tables.`}</h2>
           ) : (
@@ -44,7 +51,7 @@ function SchemaBrowser(props) {
               {schemas.map(schema => (
                 <SchemaGridItem key={schema.id}>
                   <SchemaLink
-                    to={`/browse/${dbId}/schema/${encodeURIComponent(
+                    to={`/browse/databases/${dbId}/schema/${encodeURIComponent(
                       schema.name,
                     )}`}
                   >
@@ -61,9 +68,9 @@ function SchemaBrowser(props) {
               ))}
             </Grid>
           )}
-        </div>
+        </>
       )}
-    </div>
+    </SchemaBrowserContainer>
   );
 }
 
diff --git a/frontend/src/metabase/browse/containers/SchemaBrowser.styled.tsx b/frontend/src/metabase/browse/components/SchemaBrowser.styled.tsx
similarity index 89%
rename from frontend/src/metabase/browse/containers/SchemaBrowser.styled.tsx
rename to frontend/src/metabase/browse/components/SchemaBrowser.styled.tsx
index 91571057da6d488b755fae68c7dbd5ba1daa76ec..022f0091beba1d3a89541df5fbd830155c4a84b6 100644
--- a/frontend/src/metabase/browse/containers/SchemaBrowser.styled.tsx
+++ b/frontend/src/metabase/browse/components/SchemaBrowser.styled.tsx
@@ -27,3 +27,7 @@ export const SchemaLink = styled(Link)`
     color: ${color("accent2")};
   }
 `;
+
+export const SchemaBrowserContainer = styled.div`
+  width: 100%;
+`;
diff --git a/frontend/src/metabase/browse/components/TableBrowser/TableBrowser.jsx b/frontend/src/metabase/browse/components/TableBrowser/TableBrowser.jsx
index 9a90d0ae07abe5fe2027c13c1f917aa2f75938b5..7c5a0b7703129f9e0cff0154afd99aa5e3af46e7 100644
--- a/frontend/src/metabase/browse/components/TableBrowser/TableBrowser.jsx
+++ b/frontend/src/metabase/browse/components/TableBrowser/TableBrowser.jsx
@@ -7,16 +7,17 @@ import { isSyncInProgress } from "metabase/lib/syncing";
 import Database from "metabase/entities/databases";
 import EntityItem from "metabase/components/EntityItem";
 import { Icon } from "metabase/ui";
-import { Grid } from "metabase/components/Grid";
+import BrowserCrumbs from "metabase/components/BrowserCrumbs";
 import {
   isVirtualCardId,
   SAVED_QUESTIONS_VIRTUAL_DB_ID,
 } from "metabase-lib/metadata/utils/saved-questions";
 
-import { BrowseHeader } from "../BrowseHeader";
+import { BrowseHeaderContent } from "../BrowseHeader.styled";
 import {
   TableActionLink,
   TableCard,
+  TableGrid,
   TableGridItem,
   TableLink,
 } from "./TableBrowser.styled";
@@ -43,15 +44,17 @@ const TableBrowser = ({
   showSchemaInHeader = true,
 }) => {
   return (
-    <div>
-      <BrowseHeader
-        crumbs={[
-          { title: t`Our data`, to: "/browse" },
-          getDatabaseCrumbs(dbId),
-          showSchemaInHeader && { title: schemaName },
-        ]}
-      />
-      <Grid>
+    <>
+      <BrowseHeaderContent>
+        <BrowserCrumbs
+          crumbs={[
+            { title: t`Databases`, to: "/browse/databases" },
+            getDatabaseCrumbs(dbId),
+            showSchemaInHeader && { title: schemaName },
+          ]}
+        />
+      </BrowseHeaderContent>
+      <TableGrid>
         {tables.map(table => (
           <TableGridItem key={table.id}>
             <TableCard hoverable={!isSyncInProgress(table)}>
@@ -70,8 +73,8 @@ const TableBrowser = ({
             </TableCard>
           </TableGridItem>
         ))}
-      </Grid>
-    </div>
+      </TableGrid>
+    </>
   );
 };
 
diff --git a/frontend/src/metabase/browse/components/TableBrowser/TableBrowser.styled.tsx b/frontend/src/metabase/browse/components/TableBrowser/TableBrowser.styled.tsx
index 5116e7ab30e7ca5801131438fa487fa304484f07..261d632392a819b7ac062f578af41c94ba1362ab 100644
--- a/frontend/src/metabase/browse/components/TableBrowser/TableBrowser.styled.tsx
+++ b/frontend/src/metabase/browse/components/TableBrowser/TableBrowser.styled.tsx
@@ -6,7 +6,12 @@ import {
 } from "metabase/styled-components/theme";
 import Card from "metabase/components/Card";
 import Link from "metabase/core/components/Link";
-import { GridItem } from "metabase/components/Grid";
+import { Grid, GridItem } from "metabase/components/Grid";
+
+export const TableGrid = styled(Grid)`
+  width: 100%;
+  flex: 1;
+`;
 
 export const TableGridItem = styled(GridItem)`
   width: 100%;
diff --git a/frontend/src/metabase/browse/containers/DatabaseBrowser.jsx b/frontend/src/metabase/browse/containers/DatabaseBrowser.jsx
deleted file mode 100644
index 420a6c1d45b3977c254bf25ba0b9345d02ff3a2d..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/browse/containers/DatabaseBrowser.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-/* eslint-disable react/prop-types */
-import { t } from "ttag";
-
-import Database from "metabase/entities/databases";
-
-import { color } from "metabase/lib/colors";
-import * as Urls from "metabase/lib/urls";
-
-import { Grid } from "metabase/components/Grid";
-import { Icon } from "metabase/ui";
-import Link from "metabase/core/components/Link";
-
-import { BrowseHeader } from "metabase/browse/components/BrowseHeader";
-
-import { DatabaseCard, DatabaseGridItem } from "./DatabaseBrowser.styled";
-
-function DatabaseBrowser({ databases }) {
-  return (
-    <div data-testid="database-browser">
-      <BrowseHeader crumbs={[{ title: t`Our data` }]} />
-
-      <Grid>
-        {databases.map(database => (
-          <DatabaseGridItem key={database.id}>
-            <Link to={Urls.browseDatabase(database)} display="block">
-              <DatabaseCard>
-                <Icon
-                  name="database"
-                  color={color("accent2")}
-                  className="mb3"
-                  size={32}
-                />
-                <h3 className="text-wrap">{database.name}</h3>
-              </DatabaseCard>
-            </Link>
-          </DatabaseGridItem>
-        ))}
-      </Grid>
-    </div>
-  );
-}
-
-export default Database.loadList()(DatabaseBrowser);
diff --git a/frontend/src/metabase/browse/utils.ts b/frontend/src/metabase/browse/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5489acf2d83adca52638d04ee65a24f6ae40ad03
--- /dev/null
+++ b/frontend/src/metabase/browse/utils.ts
@@ -0,0 +1,41 @@
+import _ from "underscore";
+import { t } from "ttag";
+import {
+  canonicalCollectionId,
+  coerceCollectionId,
+  isRootCollection,
+} from "metabase/collections/utils";
+import type { CollectionEssentials, SearchResult } from "metabase-types/api";
+
+export const getCollectionName = (collection: CollectionEssentials) => {
+  if (isRootCollection(collection)) {
+    return t`Our analytics`;
+  }
+  return collection?.name || t`Untitled collection`;
+};
+
+/** The root collection's id might be null or 'root' in different contexts.
+ * Use 'root' instead of null, for the sake of sorting */
+export const getCollectionIdForSorting = (collection: CollectionEssentials) => {
+  return coerceCollectionId(canonicalCollectionId(collection.id));
+};
+
+/** Group models by collection */
+export const groupModels = (
+  models: SearchResult[],
+  locale: string | undefined,
+) => {
+  const groupedModels = _.groupBy(models, model =>
+    getCollectionIdForSorting(model.collection),
+  );
+  const groupsOfModels: SearchResult[][] = Object.values(groupedModels);
+  const sortFunction = (a: SearchResult[], b: SearchResult[]) => {
+    const collection1 = a[0].collection;
+    const collection2 = b[0].collection;
+    const name1 = getCollectionName(collection1);
+    const name2 = getCollectionName(collection2);
+    return name1.localeCompare(name2, locale);
+  };
+  groupsOfModels.sort(sortFunction);
+  return groupsOfModels;
+};
diff --git a/frontend/src/metabase/core/components/Ellipsified/Ellipsified.tsx b/frontend/src/metabase/core/components/Ellipsified/Ellipsified.tsx
index 17aceb10f22ace237d5e4d022523645fe0944ac5..dfda1f2e7adf183b2110836f3f09099c305f34fd 100644
--- a/frontend/src/metabase/core/components/Ellipsified/Ellipsified.tsx
+++ b/frontend/src/metabase/core/components/Ellipsified/Ellipsified.tsx
@@ -17,6 +17,7 @@ interface EllipsifiedProps {
   lines?: number;
   placement?: Placement;
   "data-testid"?: string;
+  id?: string;
 }
 
 export const Ellipsified = ({
@@ -30,6 +31,7 @@ export const Ellipsified = ({
   lines,
   placement = "top",
   "data-testid": dataTestId,
+  id,
 }: EllipsifiedProps) => {
   const canSkipTooltipRendering = !showTooltip && !alwaysShowTooltip;
   const { isTruncated, ref } = useIsTruncated<HTMLDivElement>({
@@ -49,6 +51,7 @@ export const Ellipsified = ({
         lines={lines}
         style={style}
         data-testid={dataTestId}
+        id={id}
       >
         {children}
       </EllipsifiedRoot>
diff --git a/frontend/src/metabase/lib/urls/browse.ts b/frontend/src/metabase/lib/urls/browse.ts
index 664e4d35dbf95dbd86ecce404a32f3a37de3195d..b5b6b4654b3a29e8f916339ea1a335749cbb48c2 100644
--- a/frontend/src/metabase/lib/urls/browse.ts
+++ b/frontend/src/metabase/lib/urls/browse.ts
@@ -12,7 +12,7 @@ export function browseDatabase(database: Database) {
       ? "Saved Questions"
       : database.name;
 
-  return appendSlug(`/browse/${database.id}`, slugg(name));
+  return appendSlug(`/browse/databases/${database.id}`, slugg(name));
 }
 
 export function browseSchema(table: {
@@ -21,12 +21,12 @@ export function browseSchema(table: {
   db?: Pick<Database, "id">;
 }) {
   const databaseId = table.db?.id || table.db_id;
-  return `/browse/${databaseId}/schema/${encodeURIComponent(
+  return `/browse/databases/${databaseId}/schema/${encodeURIComponent(
     table.schema_name ?? "",
   )}`;
 }
 
 export function browseTable(table: Table) {
   const databaseId = table.db?.id || table.db_id;
-  return `/browse/${databaseId}/schema/${table.schema_name}`;
+  return `/browse/databases/${databaseId}/schema/${table.schema_name}`;
 }
diff --git a/frontend/src/metabase/lib/urls/collections.ts b/frontend/src/metabase/lib/urls/collections.ts
index afbd56dcb713e4bac921ba04dc9e7d2e89b504a0..8bcc80bfbd77b2f2eeaf2ba010dcaa6a27f1bda7 100644
--- a/frontend/src/metabase/lib/urls/collections.ts
+++ b/frontend/src/metabase/lib/urls/collections.ts
@@ -32,7 +32,7 @@ function slugifyPersonalCollection(collection: Collection) {
   return slug;
 }
 
-export function collection(collection?: Collection) {
+export function collection(collection?: Pick<Collection, "id" | "name">) {
   const isSystemCollection =
     !collection || collection.id === null || typeof collection.id === "string";
 
diff --git a/frontend/src/metabase/nav/containers/MainNavbar/MainNavbar.unit.spec.tsx b/frontend/src/metabase/nav/containers/MainNavbar/MainNavbar.unit.spec.tsx
index 6cf6da2fb7154bca15e09fdb788849e714ce8c63..68196dc86a1f44ff134f35fad3b45e415c368b37 100644
--- a/frontend/src/metabase/nav/containers/MainNavbar/MainNavbar.unit.spec.tsx
+++ b/frontend/src/metabase/nav/containers/MainNavbar/MainNavbar.unit.spec.tsx
@@ -214,7 +214,7 @@ describe("nav > containers > MainNavbar", () => {
     });
 
     it("should be highlighted if child route selected", async () => {
-      await setup({ pathname: "/browse/1" });
+      await setup({ pathname: "/browse/databases/1" });
       const link = screen.getByRole("listitem", { name: /Browse data/i });
       expect(link).toHaveAttribute("aria-selected", "true");
     });
diff --git a/frontend/src/metabase/redux/app.ts b/frontend/src/metabase/redux/app.ts
index 2ce9881a586de2f0ba0046c5e852d1ea3d0dc78a..a5f6724dc549492a7f5a0f282c1cbf42ce55d30c 100644
--- a/frontend/src/metabase/redux/app.ts
+++ b/frontend/src/metabase/redux/app.ts
@@ -58,7 +58,9 @@ const errorPage = handleActions(
   null,
 );
 
-const PATH_WITH_COLLAPSED_NAVBAR = /\/(model|question|dashboard|metabot).*/;
+// regexr.com/7r89i
+// A word boundary is added to /model so it doesn't match /browse/models
+const PATH_WITH_COLLAPSED_NAVBAR = /\/(model\b|question|dashboard|metabot).*/;
 
 export function isNavbarOpenForPathname(pathname: string, prevState: boolean) {
   return (
diff --git a/frontend/src/metabase/routes.jsx b/frontend/src/metabase/routes.jsx
index 18d3caff4aa22341236d52a27dae957672f7f822..2ddae3dcbe6ac5397a729601553c06332a87e3e0 100644
--- a/frontend/src/metabase/routes.jsx
+++ b/frontend/src/metabase/routes.jsx
@@ -23,9 +23,8 @@ import { DashboardAppConnected } from "metabase/dashboard/containers/DashboardAp
 import { AutomaticDashboardAppConnected } from "metabase/dashboard/containers/AutomaticDashboardApp";
 
 /* Browse data */
-import BrowseApp from "metabase/browse/components/BrowseApp";
-import DatabaseBrowser from "metabase/browse/containers/DatabaseBrowser";
-import SchemaBrowser from "metabase/browse/containers/SchemaBrowser";
+import { BrowseApp } from "metabase/browse/components/BrowseApp";
+import SchemaBrowser from "metabase/browse/components/SchemaBrowser";
 import TableBrowser from "metabase/browse/containers/TableBrowser";
 
 import QueryBuilder from "metabase/query_builder/containers/QueryBuilder";
@@ -219,10 +218,36 @@ export const getRoutes = store => {
             <Route path="metabot" component={QueryBuilder} />
           </Route>
 
-          <Route path="browse" component={BrowseApp}>
-            <IndexRoute component={DatabaseBrowser} />
-            <Route path=":slug" component={SchemaBrowser} />
-            <Route path=":dbId/schema/:schemaName" component={TableBrowser} />
+          <Route path="browse">
+            <IndexRedirect to="/browse/models" />
+            <Route path="models" component={() => <BrowseApp tab="models" />} />
+            <Route
+              path="databases"
+              component={() => <BrowseApp tab="databases" />}
+            />
+            <Route
+              path="databases/:slug"
+              component={({ params }) => (
+                <BrowseApp tab="databases">
+                  <SchemaBrowser params={params} />
+                </BrowseApp>
+              )}
+            />
+            <Route
+              path="databases/:dbId/schema/:schemaName"
+              component={({ params }) => (
+                <BrowseApp tab="databases">
+                  <TableBrowser params={params} />
+                </BrowseApp>
+              )}
+            />
+
+            {/* These two Redirects support legacy paths in v48 and earlier */}
+            <Redirect from=":dbId-:slug" to="databases/:dbId-:slug" />
+            <Redirect
+              from=":dbId/schema/:schemaName"
+              to="databases/:dbId/schema/:schemaName"
+            />
           </Route>
 
           {/* INDIVIDUAL DASHBOARDS */}
diff --git a/frontend/src/metabase/search/components/InfoText/InfoText.unit.spec.tsx b/frontend/src/metabase/search/components/InfoText/InfoText.unit.spec.tsx
index 341133f7859bb74bb9c4f720d546818f336f8b47..5d851a0b2811f5c842811556b58b879baefdbba5 100644
--- a/frontend/src/metabase/search/components/InfoText/InfoText.unit.spec.tsx
+++ b/frontend/src/metabase/search/components/InfoText/InfoText.unit.spec.tsx
@@ -198,7 +198,7 @@ describe("InfoText", () => {
       expect(databaseLink).toBeInTheDocument();
       expect(databaseLink).toHaveAttribute(
         "href",
-        `/browse/${MOCK_DATABASE.id}-database-name`,
+        `/browse/databases/${MOCK_DATABASE.id}-database-name`,
       );
 
       expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
diff --git a/frontend/test/metabase/lib/urls.unit.spec.js b/frontend/test/metabase/lib/urls.unit.spec.js
index 408c3c5e74229418851ce05f84c882a7b5c30f0b..43b2f8983e4c0c5b341ed942d391daec5e1dc726 100644
--- a/frontend/test/metabase/lib/urls.unit.spec.js
+++ b/frontend/test/metabase/lib/urls.unit.spec.js
@@ -364,8 +364,8 @@ describe("urls", () => {
       { path: "dashboard/1", expected: false },
       { path: "/dashboard/1", expected: false },
       { path: "/dashboard/12-orders", expected: false },
-      { path: "/browse/1", expected: false },
-      { path: "/browse/12-shop", expected: false },
+      { path: "/browse/databases/1", expected: false },
+      { path: "/browse/databases/12-shop", expected: false },
       { path: "/question/1-orders", expected: false },
     ];
 
@@ -458,7 +458,7 @@ describe("urls", () => {
 
       it(`should handle ${caseName} correctly for database browse URLs`, () => {
         expect(browseDatabase(entity)).toBe(
-          expectedUrl("/browse/1", expectedString),
+          expectedUrl("/browse/databases/1", expectedString),
         );
       });