diff --git a/e2e/test/scenarios/dashboard-cards/reproductions/15993-click-behavior-question-with-filter-causes-error.cy.spec.js b/e2e/test/scenarios/dashboard-cards/reproductions/15993-click-behavior-question-with-filter-causes-error.cy.spec.js
index 7774f43d0f491ddbd79b9ef93d4d1dddf5c7e385..d2d3ba2e98374867c249b6e4f90b283ee55cbc2b 100644
--- a/e2e/test/scenarios/dashboard-cards/reproductions/15993-click-behavior-question-with-filter-causes-error.cy.spec.js
+++ b/e2e/test/scenarios/dashboard-cards/reproductions/15993-click-behavior-question-with-filter-causes-error.cy.spec.js
@@ -39,7 +39,7 @@ describe("issue 15993", () => {
     });
 
     // Drill-through
-    cy.findAllByTestId("cell-data").get(".link").contains("0").realClick();
+    cy.findAllByTestId("cell-data").contains("0").realClick();
 
     // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
     cy.contains("117.03").should("not.exist"); // Total for the order in which quantity wasn't 0
diff --git a/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationModal/ImpersonationModalView.tsx b/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationModal/ImpersonationModalView.tsx
index a341d79b7473d1bebce53449c327e479ed7e4fc7..df2efce4bf9e9e5aa6c3b5419f5a325c0e542c51 100644
--- a/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationModal/ImpersonationModalView.tsx
+++ b/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationModal/ImpersonationModalView.tsx
@@ -10,6 +10,7 @@ import FormFooter from "metabase/core/components/FormFooter";
 import FormSelect from "metabase/core/components/FormSelect";
 import FormSubmitButton from "metabase/core/components/FormSubmitButton";
 import Link from "metabase/core/components/Link/Link";
+import CS from "metabase/css/core/index.css";
 import { Form, FormProvider } from "metabase/forms";
 import * as Errors from "metabase/lib/errors";
 import MetabaseSettings from "metabase/lib/settings";
@@ -93,7 +94,7 @@ export const ImpersonationModalView = ({
       <ImpersonationDescription>
         {modalMessage}{" "}
         <ExternalLink
-          className="link"
+          className={CS.link}
           // eslint-disable-next-line no-unconditional-metabase-links-render -- Admin settings
           href={MetabaseSettings.docsUrl("permissions/data")}
         >{t`Learn More`}</ExternalLink>
diff --git a/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationWarning/ImpersonationWarning.tsx b/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationWarning/ImpersonationWarning.tsx
index 1fedad4415bc8924f68526474b9e7f9dc01cbef8..3bc90ddc01d9aa90a31ab924790e281fd833a129 100644
--- a/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationWarning/ImpersonationWarning.tsx
+++ b/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationWarning/ImpersonationWarning.tsx
@@ -2,6 +2,7 @@ import { t, jt } from "ttag";
 
 import { BoldCode } from "metabase/components/Code";
 import Link from "metabase/core/components/Link";
+import CS from "metabase/css/core/index.css";
 import * as Urls from "metabase/lib/urls";
 import { isEmpty } from "metabase/lib/validate";
 import type Database from "metabase-lib/v1/metadata/Database";
@@ -61,7 +62,7 @@ export const ImpersonationWarning = ({
     <ImpersonationAlert icon="warning" variant="warning">
       {isEmpty(databaseUser) ? emptyText : warningText}{" "}
       <Link
-        className="link"
+        className={CS.link}
         to={Urls.editDatabase(database.id) + (databaseUser ? "#user" : "")}
       >{t`Edit settings`}</Link>
     </ImpersonationAlert>
diff --git a/frontend/src/metabase/admin/databases/components/DatabaseList/DatabaseList.jsx b/frontend/src/metabase/admin/databases/components/DatabaseList/DatabaseList.jsx
index 3eb2a55d1ba20772916e5524001f441b2b1e1c4e..d4d693150fc712dacf46344dfee211dca47ec892 100644
--- a/frontend/src/metabase/admin/databases/components/DatabaseList/DatabaseList.jsx
+++ b/frontend/src/metabase/admin/databases/components/DatabaseList/DatabaseList.jsx
@@ -120,7 +120,7 @@ export default class DatabaseList extends Component {
                             )}
                             <Link
                               to={"/admin/databases/" + database.id}
-                              className="text-bold link"
+                              className={cx("text-bold", CS.link)}
                             >
                               {database.name}
                             </Link>
@@ -153,7 +153,7 @@ export default class DatabaseList extends Component {
                 })}
               >
                 {isAddingSampleDatabase ? (
-                  <span className="text-light no-decoration">
+                  <span className={cx("text-light", CS.noDecoration)}>
                     {t`Restoring the sample database...`}
                   </span>
                 ) : (
diff --git a/frontend/src/metabase/admin/people/components/AddRow.jsx b/frontend/src/metabase/admin/people/components/AddRow.jsx
index 7cb53103de3f65876a2b282ec49a1b99a0c4ddbf..5076d658c484bb7d51831974854e583c0dc8440b 100644
--- a/frontend/src/metabase/admin/people/components/AddRow.jsx
+++ b/frontend/src/metabase/admin/people/components/AddRow.jsx
@@ -44,7 +44,7 @@ export const AddRow = forwardRef(function AddRow(
         onKeyDown={onKeyDown}
         onChange={onChange}
       />
-      <span className="link no-decoration cursor-pointer" onClick={onCancel}>
+      <span className={CS.link} onClick={onCancel}>
         {t`Cancel`}
       </span>
       <button
diff --git a/frontend/src/metabase/admin/people/components/GroupsListing.jsx b/frontend/src/metabase/admin/people/components/GroupsListing.jsx
index 16ee454937a33d4c10695551d28ad7a5cafc54e6..11cc11c0e724f5ab573428a024cce799e0256462 100644
--- a/frontend/src/metabase/admin/people/components/GroupsListing.jsx
+++ b/frontend/src/metabase/admin/people/components/GroupsListing.jsx
@@ -164,10 +164,7 @@ function EditingGroupRow({
       </td>
       <td />
       <td className="text-right">
-        <span
-          className="link no-decoration cursor-pointer"
-          onClick={onCancelClicked}
-        >{t`Cancel`}</span>
+        <span className={CS.link} onClick={onCancelClicked}>{t`Cancel`}</span>
         <button
           className={cx(ButtonsS.Button, CS.ml2, {
             [ButtonsS.ButtonPrimary]: textIsValid && textHasChanged,
@@ -213,7 +210,7 @@ function GroupRow({
       <td>
         <Link
           to={"/admin/people/groups/" + group.id}
-          className={cx("link", CS.noDecoration, CS.flex, CS.alignCenter)}
+          className={cx(CS.link, CS.flex, CS.alignCenter)}
         >
           <span className="text-white">
             <UserAvatar
diff --git a/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx b/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx
index bbe6b8769be694d83c9609c0ef669b9ecd0afb24..ee65d603ba7db7f2fafd34acc1c498f2e0514f18 100644
--- a/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx
+++ b/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx
@@ -1,4 +1,5 @@
 /* eslint-disable react/prop-types */
+import cx from "classnames";
 import { Component } from "react";
 import { connect } from "react-redux";
 import { push } from "react-router-redux";
@@ -9,6 +10,7 @@ import ModalContent from "metabase/components/ModalContent";
 import PasswordReveal from "metabase/components/PasswordReveal";
 import Button from "metabase/core/components/Button";
 import Link from "metabase/core/components/Link";
+import CS from "metabase/css/core/index.css";
 import User from "metabase/entities/users";
 import MetabaseSettings from "metabase/lib/settings";
 
@@ -50,7 +52,7 @@ const EmailSuccess = ({ user, isSsoEnabled }) => {
       )} with instructions to log in. If this user is unable to authenticate then you can ${(
         <Link
           to={`/admin/people/${user.id}/reset`}
-          className="link"
+          className={CS.link}
         >{t`reset their password.`}</Link>
       )}`}</div>
     );
@@ -76,7 +78,7 @@ const PasswordSuccess = ({ user, temporaryPassword }) => (
       className="pt4 text-centered"
     >
       {jt`If you want to be able to send email invites, just go to the ${(
-        <Link to="/admin/settings/email" className="link text-bold">
+        <Link to="/admin/settings/email" className={cx(CS.link, "text-bold")}>
           Email Settings
         </Link>
       )} page.`}
diff --git a/frontend/src/metabase/admin/permissions/components/CollectionPermissionsModal/CollectionPermissionsModal.jsx b/frontend/src/metabase/admin/permissions/components/CollectionPermissionsModal/CollectionPermissionsModal.jsx
index 70aa92a8a6b6b4d799965fd4fe4e959cc61880ad..58720d3231849205879693ff78ffec2e23e46a79 100644
--- a/frontend/src/metabase/admin/permissions/components/CollectionPermissionsModal/CollectionPermissionsModal.jsx
+++ b/frontend/src/metabase/admin/permissions/components/CollectionPermissionsModal/CollectionPermissionsModal.jsx
@@ -130,7 +130,7 @@ const CollectionPermissionsModal = ({
           : [
               <Link
                 key="all-permissions"
-                className="link"
+                className={CS.link}
                 to="/admin/permissions/collections"
               >
                 {t`See all collection permissions`}
diff --git a/frontend/src/metabase/admin/settings/components/UploadSettings/UploadSettings.tsx b/frontend/src/metabase/admin/settings/components/UploadSettings/UploadSettings.tsx
index 9d3f91c7fd88ae719cad09546707652c41a76ab7..a3db423291d22d969a6aa9a68247af20ac8fc707 100644
--- a/frontend/src/metabase/admin/settings/components/UploadSettings/UploadSettings.tsx
+++ b/frontend/src/metabase/admin/settings/components/UploadSettings/UploadSettings.tsx
@@ -12,6 +12,7 @@ import Input from "metabase/core/components/Input";
 import Link from "metabase/core/components/Link";
 import type { SelectChangeEvent } from "metabase/core/components/Select";
 import Select from "metabase/core/components/Select";
+import CS from "metabase/css/core/index.css";
 import Databases from "metabase/entities/databases";
 import Schemas from "metabase/entities/schemas";
 import { useDispatch } from "metabase/lib/redux";
@@ -71,7 +72,7 @@ const Header = () => (
       display_name: t`Allow people to upload data to Collections`,
       description: jt`People will be able to upload CSV files that will be stored in the ${(
         <Link
-          className="link"
+          className={CS.link}
           key="db-link"
           to="/admin/databases"
         >{t`database`}</Link>
diff --git a/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing/PublicLinksListing.jsx b/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing/PublicLinksListing.jsx
index 416b14b6437cb820f42144b974a5ef126b51d3e5..39a0a9a762f05901c94d3819cfe7e390cd92be08 100644
--- a/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing/PublicLinksListing.jsx
+++ b/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing/PublicLinksListing.jsx
@@ -100,7 +100,7 @@ class PublicLinksListing extends Component {
                         <ExternalLink
                           href={getPublicUrl(link)}
                           onClick={() => this.trackEvent("Public Link Clicked")}
-                          className="link text-wrap"
+                          className={cx(CS.link, "text-wrap")}
                         >
                           {getPublicUrl(link)}
                         </ExternalLink>
diff --git a/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx b/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx
index f7db719f188082a8099dab17d7fff6db22355352..a1410cfc27bc202579b177e88480541192a93682 100644
--- a/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx
+++ b/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx
@@ -50,7 +50,7 @@ const renderJobsTable = jobs => {
                 <td>{job.durable}</td>
                 <td>
                   <Link
-                    className="link"
+                    className={CS.link}
                     to={`/admin/troubleshooting/jobs/${job.key}`}
                   >
                     {t`View triggers`}
diff --git a/frontend/src/metabase/admin/tasks/containers/TasksApp.jsx b/frontend/src/metabase/admin/tasks/containers/TasksApp.jsx
index e092749b3a2448497bba5c62dbb75647313b429d..abcfb72fd645fca282f73b202447499869902bad 100644
--- a/frontend/src/metabase/admin/tasks/containers/TasksApp.jsx
+++ b/frontend/src/metabase/admin/tasks/containers/TasksApp.jsx
@@ -91,7 +91,7 @@ class TasksAppInner extends Component {
                   <td>{task.duration}</td>
                   <td>
                     <Link
-                      className="link text-bold"
+                      className={cx(CS.link, "text-bold")}
                       to={`/admin/troubleshooting/tasks/${task.id}`}
                     >{t`View`}</Link>
                   </td>
diff --git a/frontend/src/metabase/collections/components/CollectionCopyEntityModal.jsx b/frontend/src/metabase/collections/components/CollectionCopyEntityModal.jsx
index e200b0fef43da2367f6bc83756406c17a835589e..0dcd86b4ea99fe8e9bc623257eff52f8fd52dba3 100644
--- a/frontend/src/metabase/collections/components/CollectionCopyEntityModal.jsx
+++ b/frontend/src/metabase/collections/components/CollectionCopyEntityModal.jsx
@@ -59,7 +59,7 @@ function CollectionCopyEntityModal({
         {newEntityObject.uncopied?.length > 0
           ? t`Duplicated ${entityObject.model}, but couldn't duplicate some questions`
           : t`Duplicated ${entityObject.model}`}
-        <Link className="link text-bold ml1" to={newEntityUrl}>
+        <Link className={cx(CS.link, "text-bold ml1")} to={newEntityUrl}>
           {t`See it`}
         </Link>
       </div>,
diff --git a/frontend/src/metabase/components/ChannelSetupMessage/ChannelSetupMessage.jsx b/frontend/src/metabase/components/ChannelSetupMessage/ChannelSetupMessage.jsx
index bc83fe8585b0ec14af0b7581211ee5dff31905f3..6a2e779f8af5c1a6d8ab2c2aa984af76d3e96663 100644
--- a/frontend/src/metabase/components/ChannelSetupMessage/ChannelSetupMessage.jsx
+++ b/frontend/src/metabase/components/ChannelSetupMessage/ChannelSetupMessage.jsx
@@ -42,7 +42,7 @@ export default class ChannelSetupMessage extends Component {
       content = (
         <div className="mb1">
           <h4 className="text-medium">{t`Your admin's email address`}:</h4>
-          <a className="h2 link no-decoration" href={"mailto:" + adminEmail}>
+          <a className={cx("h2", CS.link)} href={"mailto:" + adminEmail}>
             {adminEmail}
           </a>
         </div>
diff --git a/frontend/src/metabase/components/ErrorDetails/ErrorDetails.tsx b/frontend/src/metabase/components/ErrorDetails/ErrorDetails.tsx
index 625a7618af4178d2de01a77f2d913685e1a66c31..56f227894c3b1a320aff5393869c0fc460acf9c4 100644
--- a/frontend/src/metabase/components/ErrorDetails/ErrorDetails.tsx
+++ b/frontend/src/metabase/components/ErrorDetails/ErrorDetails.tsx
@@ -2,6 +2,8 @@ import cx from "classnames";
 import { useState } from "react";
 import { t } from "ttag";
 
+import CS from "metabase/css/core/index.css";
+
 import { ErrorBox } from "./ErrorBox";
 import type { ErrorDetailsProps } from "./types";
 
@@ -24,7 +26,7 @@ export default function ErrorDetails({
   return (
     <div className={className}>
       <div className={centered ? "text-centered" : "text-left"}>
-        <a onClick={toggleShowError} className="link cursor-pointer">
+        <a onClick={toggleShowError} className={cx(CS.link)}>
           {showError ? t`Hide error details` : t`Show error details`}
         </a>
       </div>
diff --git a/frontend/src/metabase/components/LeftNavPane/LeftNavPane.jsx b/frontend/src/metabase/components/LeftNavPane/LeftNavPane.jsx
index 12565f9df6c827afbb85d58345b3598d3a982947..e777f0968e65167e4e21e295e934307baed86b43 100644
--- a/frontend/src/metabase/components/LeftNavPane/LeftNavPane.jsx
+++ b/frontend/src/metabase/components/LeftNavPane/LeftNavPane.jsx
@@ -54,10 +54,9 @@ export function LeftNavPaneItemBack({ path }) {
           AdminS.AdminListItem,
           CS.flex,
           CS.alignCenter,
-          CS.noDecoration,
           CS.textBold,
           CS.justifyBetween,
-          "link",
+          CS.link,
         )}
       >
         &lt; {t`Back`}
diff --git a/frontend/src/metabase/components/PasswordReveal/PasswordReveal.jsx b/frontend/src/metabase/components/PasswordReveal/PasswordReveal.jsx
index b2c7a7faa975a11bb94bea0f64ea24b0cb07bfff..4f8a4bc80786c27e740a34289a30f8dc24b16af8 100644
--- a/frontend/src/metabase/components/PasswordReveal/PasswordReveal.jsx
+++ b/frontend/src/metabase/components/PasswordReveal/PasswordReveal.jsx
@@ -76,7 +76,7 @@ export default class PasswordReveal extends Component {
 
         <div className={cx(CS.mlAuto, CS.flex, CS.alignCenter)}>
           <a
-            className={cx("link", CS.textBold, CS.mr2)}
+            className={cx(CS.link, CS.textBold, CS.mr2)}
             onClick={() => this.setState({ visible: !visible })}
           >
             {visible ? t`Hide` : t`Show`}
diff --git a/frontend/src/metabase/components/Triggerable/Triggerable.jsx b/frontend/src/metabase/components/Triggerable/Triggerable.jsx
index d87290491ede4ca36ac20ca9da4c4c19ad4a0e02..99c677da067cde85327a36fca8b255e70273c672 100644
--- a/frontend/src/metabase/components/Triggerable/Triggerable.jsx
+++ b/frontend/src/metabase/components/Triggerable/Triggerable.jsx
@@ -4,6 +4,7 @@ import cx from "classnames";
 import { createRef, cloneElement, Children, Component } from "react";
 
 import Tooltip from "metabase/core/components/Tooltip";
+import CS from "metabase/css/core/index.css";
 import { isObscured } from "metabase/lib/dom";
 
 const Trigger = styled.a``;
@@ -153,7 +154,7 @@ const Triggerable = ComposedComponent =>
               triggerClasses,
               isOpen && triggerClassesOpen,
               !isOpen && triggerClassesClose,
-              "no-decoration",
+              CS.noDecoration,
               {
                 "cursor-default": this.props.disabled,
               },
diff --git a/frontend/src/metabase/core/components/ExternalLink/ExternalLink.tsx b/frontend/src/metabase/core/components/ExternalLink/ExternalLink.tsx
index 608438a43a2eba5ef160efcb54dec2b8eaf38e25..90813728595d00e8f3e40ccae0f3d770b565d39a 100644
--- a/frontend/src/metabase/core/components/ExternalLink/ExternalLink.tsx
+++ b/frontend/src/metabase/core/components/ExternalLink/ExternalLink.tsx
@@ -1,6 +1,7 @@
 import type { AnchorHTMLAttributes, ReactNode, Ref } from "react";
 import { forwardRef } from "react";
 
+import CS from "metabase/css/core/index.css";
 import { getUrlTarget } from "metabase/lib/dom";
 
 import { LinkRoot } from "./ExternalLink.styled";
@@ -20,7 +21,7 @@ const ExternalLink = forwardRef(function ExternalLink(
     <LinkRoot
       ref={ref}
       href={href}
-      className={className || "link"}
+      className={className || CS.link}
       target={target}
       // prevent malicious pages from navigating us away
       rel="noopener noreferrer"
diff --git a/frontend/src/metabase/css/core/link.module.css b/frontend/src/metabase/css/core/link.module.css
index 03bec2f0e7887b67f71fcda8378b89e4397d52ef..3495c97d7608c5dbb6bc9c69d3e433d5cba1a194 100644
--- a/frontend/src/metabase/css/core/link.module.css
+++ b/frontend/src/metabase/css/core/link.module.css
@@ -1,27 +1,25 @@
-:global(.no-decoration),
-.no-decoration,
 .noDecoration {
   text-decoration: none;
 }
 
-:global(.link) {
+.link {
   cursor: pointer;
   text-decoration: none;
   color: var(--color-brand);
 }
 
-:global(.link:hover) {
+.link:hover {
   text-decoration: underline;
 }
 
-:global(.link:focus) {
+.link:focus {
   outline: 2px solid var(--color-focus);
 }
 
-:global(.link:focus:not(:focus-visible)) {
+.link:focus:not(:focus-visible) {
   outline: none;
 }
 
-:global(.link--wrappable) {
+.linkWrappable {
   word-break: break-all;
 }
diff --git a/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx b/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx
index 2a2457dcf857695b60cf9a88156ad67af6795c43..6c732e4639569c451fffc3bb926b737362e957e6 100644
--- a/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx
+++ b/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx
@@ -79,7 +79,10 @@ class AutomaticDashboardAppInner extends Component {
     triggerToast(
       <div className={cx(CS.flex, CS.alignCenter)}>
         {t`Your dashboard was saved`}
-        <Link className="link text-bold ml1" to={Urls.dashboard(newDashboard)}>
+        <Link
+          className={cx(CS.link, "text-bold ml1")}
+          to={Urls.dashboard(newDashboard)}
+        >
           {t`See it`}
         </Link>
       </div>,
diff --git a/frontend/src/metabase/databases/components/DatabaseClientIdDescription/DatabaseClientIdDescription.tsx b/frontend/src/metabase/databases/components/DatabaseClientIdDescription/DatabaseClientIdDescription.tsx
index 6948f82bbaad34fdab5340c785f7965c1c64d2da..71b2f23cadaedc08aa29367dd6b3a99959a7c372 100644
--- a/frontend/src/metabase/databases/components/DatabaseClientIdDescription/DatabaseClientIdDescription.tsx
+++ b/frontend/src/metabase/databases/components/DatabaseClientIdDescription/DatabaseClientIdDescription.tsx
@@ -2,6 +2,7 @@ import { useFormikContext } from "formik";
 import { jt, t } from "ttag";
 
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import type { DatabaseData } from "metabase-types/api";
 
 const CREDENTIAL_URLS: Record<string, string> = {
@@ -24,7 +25,7 @@ const DatabaseClientIdDescription = (): JSX.Element | null => {
   return (
     <span>
       {jt`${(
-        <ExternalLink className="link" href={projectUrl.href}>
+        <ExternalLink className={CS.link} href={projectUrl.href}>
           {t`Click here`}
         </ExternalLink>
       )} to generate a Client ID and Client Secret for your project.`}{" "}
diff --git a/frontend/src/metabase/lib/formatting/url.tsx b/frontend/src/metabase/lib/formatting/url.tsx
index 4d9ad10b93701fb2a9e0ccb5c2280e0e74573e64..cf820fb2a86d0a815e19e927aae9179382970384 100644
--- a/frontend/src/metabase/lib/formatting/url.tsx
+++ b/frontend/src/metabase/lib/formatting/url.tsx
@@ -1,4 +1,7 @@
+import cx from "classnames";
+
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import { getDataFromClicked } from "metabase-lib/v1/parameters/utils/click-behavior";
 import { isURL } from "metabase-lib/v1/types/utils/isa";
 
@@ -35,7 +38,7 @@ export function formatUrl(value: string, options: OptionsType = {}) {
   if (jsx && rich && url) {
     const text = getLinkText(value, options);
     return (
-      <ExternalLink className="link link--wrappable" href={url}>
+      <ExternalLink className={cx(CS.link, CS.linkWrappable)} href={url}>
         {text}
       </ExternalLink>
     );
diff --git a/frontend/src/metabase/lib/formatting/value.tsx b/frontend/src/metabase/lib/formatting/value.tsx
index 86f39280aba6604191192f9510a4afa5cbb12311..0584672d5682c183c95ef7fb5bf73e8e13bdec08 100644
--- a/frontend/src/metabase/lib/formatting/value.tsx
+++ b/frontend/src/metabase/lib/formatting/value.tsx
@@ -1,3 +1,4 @@
+import cx from "classnames";
 import type { Moment } from "moment-timezone"; // eslint-disable-line no-restricted-imports -- deprecated usage
 import moment from "moment-timezone"; // eslint-disable-line no-restricted-imports -- deprecated usage
 import Mustache from "mustache";
@@ -5,6 +6,7 @@ import type * as React from "react";
 import ReactMarkdown from "react-markdown";
 
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import { NULL_DISPLAY_VALUE, NULL_NUMERIC_VALUE } from "metabase/lib/constants";
 import { renderLinkTextForClick } from "metabase/lib/formatting/link";
 import {
@@ -155,7 +157,10 @@ export function formatValueRaw(
     // Style this like a link if we're in a jsx context.
     // It's not actually a link since we handle the click differently for dashboard and question targets.
     return (
-      <div className="link link--wrappable">
+      <div
+        data-testid="link-formatted-text"
+        className={cx(CS.link, CS.linkWrappable)}
+      >
         {formatValueRaw(value, { ...options, jsx: false })}
       </div>
     );
diff --git a/frontend/src/metabase/nav/components/PaymentBanner/PaymentBanner.tsx b/frontend/src/metabase/nav/components/PaymentBanner/PaymentBanner.tsx
index 9ae99d6b79056850cbd0afc9ce47bec22fcec9c1..13bf516c5a5aa6d1702cd4c282338fef01c7460f 100644
--- a/frontend/src/metabase/nav/components/PaymentBanner/PaymentBanner.tsx
+++ b/frontend/src/metabase/nav/components/PaymentBanner/PaymentBanner.tsx
@@ -2,6 +2,7 @@ import { jt, t } from "ttag";
 
 import Banner from "metabase/components/Banner";
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import MetabaseSettings from "metabase/lib/settings";
 import type { TokenStatus } from "metabase-types/api";
 
@@ -17,7 +18,7 @@ export const PaymentBanner = ({ isAdmin, tokenStatus }: PaymentBannerProps) => {
         {jt`⚠️ We couldn't process payment for your account. Please ${(
           <ExternalLink
             key="payment-past-due"
-            className="link"
+            className={CS.link}
             href={MetabaseSettings.storeUrl()}
           >
             {t`review your payment settings`}
@@ -31,7 +32,7 @@ export const PaymentBanner = ({ isAdmin, tokenStatus }: PaymentBannerProps) => {
         {jt`⚠️ Pro features won’t work right now due to lack of payment. ${(
           <ExternalLink
             key="payment-unpaid"
-            className="link"
+            className={CS.link}
             href={MetabaseSettings.storeUrl()}
           >
             {t`Review your payment settings`}
diff --git a/frontend/src/metabase/nav/components/QuestionLineage/QuestionLineage.tsx b/frontend/src/metabase/nav/components/QuestionLineage/QuestionLineage.tsx
index 0dad2c89a8b8a8b876364e8780e23546cb58bcaf..afc3445313a5265945cadfd4a8c2e5219c513967 100644
--- a/frontend/src/metabase/nav/components/QuestionLineage/QuestionLineage.tsx
+++ b/frontend/src/metabase/nav/components/QuestionLineage/QuestionLineage.tsx
@@ -2,6 +2,7 @@ import { t } from "ttag";
 
 import Badge from "metabase/components/Badge";
 import Link from "metabase/core/components/Link/Link";
+import CS from "metabase/css/core/index.css";
 import type { IconName } from "metabase/ui";
 import type Question from "metabase-lib/v1/Question";
 import * as ML_Urls from "metabase-lib/v1/urls";
@@ -24,7 +25,7 @@ const QuestionLineage = ({
   return (
     <Badge icon={icon} isSingleLine>
       {t`Started from`}{" "}
-      <Link className="link" to={ML_Urls.getUrl(originalQuestion)}>
+      <Link className={CS.link} to={ML_Urls.getUrl(originalQuestion)}>
         {originalQuestion.displayName()}
       </Link>
     </Badge>
diff --git a/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx b/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx
index 3d0179597e54f7f1c1a900120f84afab7454f091..c768d90968a83934c671bd7815c16f082504ee31 100644
--- a/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx
+++ b/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx
@@ -87,7 +87,7 @@ class AlertListPopoverContent extends Component {
           <div className={cx(CS.borderTop, CS.p2, "bg-light-blue")}>
             <a
               className={cx(
-                "link",
+                CS.link,
                 CS.flex,
                 CS.alignCenter,
                 CS.textBold,
@@ -186,11 +186,11 @@ class AlertListItemInner extends Component {
               }}
             >
               {(isAdmin || isCurrentUser) && (
-                <a className="link" onClick={this.onEdit}>{jt`Edit`}</a>
+                <a className={CS.link} onClick={this.onEdit}>{jt`Edit`}</a>
               )}
               {!isAdmin && !unsubscribingProgress && (
                 <a
-                  className="link ml2"
+                  className={cx(CS.link, "ml2")}
                   onClick={this.onUnsubscribe}
                 >{jt`Unsubscribe`}</a>
               )}
diff --git a/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorTablePicker/DataSelectorTablePicker.tsx b/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorTablePicker/DataSelectorTablePicker.tsx
index b3a6b68bde43eb10a52fe44a308ad0a85401cd1b..d0afdfa88bee1c98da96cc074e3974f67981671b 100644
--- a/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorTablePicker/DataSelectorTablePicker.tsx
+++ b/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorTablePicker/DataSelectorTablePicker.tsx
@@ -1,3 +1,4 @@
+import cx from "classnames";
 import type { ReactNode } from "react";
 import { t } from "ttag";
 
@@ -7,6 +8,7 @@ import {
 } from "metabase/components/MetadataInfo/TableInfoIcon/TableInfoIcon";
 import AccordionList from "metabase/core/components/AccordionList";
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import { color } from "metabase/lib/colors";
 import MetabaseSettings from "metabase/lib/settings";
 import { isSyncCompleted } from "metabase/lib/syncing";
@@ -162,7 +164,7 @@ const LinkToDocsOnReferencingSavedQuestionsInQueries = () => (
         "questions/native-editor/referencing-saved-questions-in-queries",
       )}
       target="_blank"
-      className="block link"
+      className={cx("block", CS.link)}
     >
       {t`Learn more about nested queries`}
     </ExternalLink>
diff --git a/frontend/src/metabase/query_builder/components/DataSelector/TriggerComponents.tsx b/frontend/src/metabase/query_builder/components/DataSelector/TriggerComponents.tsx
index 166eef94a3847e773731849fa563d06ccec2c51e..b725f9222f326bb17480f5df1edf7e32f77abe35 100644
--- a/frontend/src/metabase/query_builder/components/DataSelector/TriggerComponents.tsx
+++ b/frontend/src/metabase/query_builder/components/DataSelector/TriggerComponents.tsx
@@ -1,7 +1,9 @@
+import cx from "classnames";
 import type { CSSProperties, ReactNode } from "react";
 import { t } from "ttag";
 import _ from "underscore";
 
+import CS from "metabase/css/core/index.css";
 import { Icon, Text } from "metabase/ui";
 import type Database from "metabase-lib/v1/metadata/Database";
 import type Field from "metabase-lib/v1/metadata/Field";
@@ -81,22 +83,26 @@ export function FieldTrigger({
 export function DatabaseTrigger({ database }: { database: Database }) {
   return database ? (
     <span
-      className="text-wrap text-grey no-decoration"
+      className={cx("text-wrap text-grey", CS.noDecoration)}
       data-testid="selected-database"
     >
       {database.name}
     </span>
   ) : (
-    <span className="text-medium no-decoration">{t`Select a database`}</span>
+    <span
+      className={cx("text-medium", CS.noDecoration)}
+    >{t`Select a database`}</span>
   );
 }
 
 export function TableTrigger({ table }: { table: Table }) {
   return table ? (
-    <span className="text-wrap text-grey no-decoration">
+    <span className={cx("text-wrap text-grey", CS.noDecoration)}>
       {table.display_name || table.name}
     </span>
   ) : (
-    <span className="text-medium no-decoration">{t`Select a table`}</span>
+    <span
+      className={cx("text-medium", CS.noDecoration)}
+    >{t`Select a table`}</span>
   );
 }
diff --git a/frontend/src/metabase/query_builder/components/ExpandableString.jsx b/frontend/src/metabase/query_builder/components/ExpandableString.jsx
index ba542ce62881096946869cc0ef5d927ef2444efc..539268aed8e6d4d74733df95a54c5ab73b940c21 100644
--- a/frontend/src/metabase/query_builder/components/ExpandableString.jsx
+++ b/frontend/src/metabase/query_builder/components/ExpandableString.jsx
@@ -1,8 +1,11 @@
 /* eslint-disable react/prop-types */
+import cx from "classnames";
 import Humanize from "humanize-plus";
 import { Component } from "react";
 import { t } from "ttag";
 
+import CS from "metabase/css/core/index.css";
+
 export default class ExpandableString extends Component {
   constructor(props, context) {
     super(props, context);
@@ -43,7 +46,7 @@ export default class ExpandableString extends Component {
         <span>
           {this.props.str}{" "}
           <span
-            className="block mt1 link"
+            className={cx("block mt1", CS.link)}
             onClick={this.toggleExpansion}
           >{t`View less`}</span>
         </span>
@@ -53,7 +56,7 @@ export default class ExpandableString extends Component {
         <span>
           {truncated}{" "}
           <span
-            className="block mt1 link"
+            className={cx("block mt1", CS.link)}
             onClick={this.toggleExpansion}
           >{t`View more`}</span>
         </span>
diff --git a/frontend/src/metabase/query_builder/components/VisualizationError/VisualizationError.tsx b/frontend/src/metabase/query_builder/components/VisualizationError/VisualizationError.tsx
index 7f8d29b8ad89cc0d7bfbcabef552aee0789d5e98..024152ec234adc74560a5ebd15a8a51b29d55145 100644
--- a/frontend/src/metabase/query_builder/components/VisualizationError/VisualizationError.tsx
+++ b/frontend/src/metabase/query_builder/components/VisualizationError/VisualizationError.tsx
@@ -31,7 +31,7 @@ function EmailAdmin(): JSX.Element | null {
   const hasAdminEmail = isNotNull(MetabaseSettings.adminEmail());
   return hasAdminEmail ? (
     <span className={QueryBuilderS.QueryErrorAdminEmail}>
-      <a className="no-decoration" href={`mailto:${hasAdminEmail}`}>
+      <a className={CS.noDecoration} href={`mailto:${hasAdminEmail}`}>
         {hasAdminEmail}
       </a>
     </span>
diff --git a/frontend/src/metabase/query_builder/components/VisualizationResult.jsx b/frontend/src/metabase/query_builder/components/VisualizationResult.jsx
index 9b89e6c6af174f5e13808aef96c4969536034aea..00452d780bb46d2941a1c82f3608a37c1f80e25f 100644
--- a/frontend/src/metabase/query_builder/components/VisualizationResult.jsx
+++ b/frontend/src/metabase/query_builder/components/VisualizationResult.jsx
@@ -78,7 +78,7 @@ export default class VisualizationResult extends Component {
                   <p>
                     {jt`You can also ${(
                       <a
-                        className="link"
+                        className={CS.link}
                         key="link"
                         onClick={this.showCreateAlertModal}
                       >
diff --git a/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp/TagEditorHelp.tsx b/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp/TagEditorHelp.tsx
index 7fa023d36f88c7766864ad4d330bb50c66b78e19..6f26acc08cfa050426e24e04308cc924e0acf195 100644
--- a/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp/TagEditorHelp.tsx
+++ b/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp/TagEditorHelp.tsx
@@ -1,8 +1,10 @@
+import cx from "classnames";
 import { t, jt } from "ttag";
 
 import Code from "metabase/components/Code";
 import Button from "metabase/core/components/Button";
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import { useSelector } from "metabase/lib/redux";
 import MetabaseSettings from "metabase/lib/settings";
 import { uuid } from "metabase/lib/utils";
@@ -312,7 +314,7 @@ export const TagEditorHelp = ({
       />
 
       {showMetabaseLinks && (
-        <p className="pt2 link">
+        <p className={cx("pt2", CS.link)}>
           <ExternalLink
             href={MetabaseSettings.docsUrl(
               "questions/native-editor/sql-parameters",
diff --git a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParamParts/FilterWidgetTypeSelect.tsx b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParamParts/FilterWidgetTypeSelect.tsx
index 81a421fea8e05341b15bfe12d402835614cdd1b6..3c6309bb27204a8350a6d5cef799c9bc091a320f 100644
--- a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParamParts/FilterWidgetTypeSelect.tsx
+++ b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParamParts/FilterWidgetTypeSelect.tsx
@@ -2,6 +2,7 @@ import { useMemo } from "react";
 import { Link } from "react-router";
 import { t } from "ttag";
 
+import CS from "metabase/css/core/index.css";
 import MetabaseSettings from "metabase/lib/settings";
 import { Select } from "metabase/ui";
 import type { TemplateTag } from "metabase-types/api";
@@ -65,7 +66,7 @@ export function FilterWidgetTypeSelect({
               "the-field-filter-variable-type",
             )}
             target="_blank"
-            className="link"
+            className={CS.link}
           >
             {t`Learn more`}
           </Link>
diff --git a/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx b/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx
index 8286019e82ae42c1a45b16e2e0b38f5362c3ba34..6e8af10cea977171229ff3a477c024dd018a28bd 100644
--- a/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx
+++ b/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx
@@ -1,6 +1,7 @@
 import { t } from "ttag";
 
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import MetabaseSettings from "metabase/lib/settings";
 
 import { SetupFooterRoot } from "./SetupHelp.styled";
@@ -10,7 +11,7 @@ export const SetupHelp = (): JSX.Element => {
     <SetupFooterRoot>
       {t`If you feel stuck`},{" "}
       <ExternalLink
-        className="link"
+        className={CS.link}
         href={MetabaseSettings.docsUrl(
           "configuring-metabase/setting-up-metabase",
         )}
diff --git a/frontend/src/metabase/sharing/components/AddEditSidebar/CaveatMessage/CaveatMessage.jsx b/frontend/src/metabase/sharing/components/AddEditSidebar/CaveatMessage/CaveatMessage.jsx
index 302f8647103370bd90ea031f7e84e299e8f432b2..293ebbe98746b494933e4721bfda7dc8e0312876 100644
--- a/frontend/src/metabase/sharing/components/AddEditSidebar/CaveatMessage/CaveatMessage.jsx
+++ b/frontend/src/metabase/sharing/components/AddEditSidebar/CaveatMessage/CaveatMessage.jsx
@@ -1,6 +1,7 @@
 import { t } from "ttag";
 
 import ExternalLink from "metabase/core/components/ExternalLink";
+import CS from "metabase/css/core/index.css";
 import { useSelector } from "metabase/lib/redux";
 import MetabaseSettings from "metabase/lib/settings";
 import { getShowMetabaseLinks } from "metabase/selectors/whitelabel";
@@ -16,7 +17,7 @@ export function CaveatMessage() {
         <>
           &nbsp;
           <ExternalLink
-            className="link"
+            className={CS.link}
             target="_blank"
             href={MetabaseSettings.docsUrl("dashboards/subscriptions")}
           >
diff --git a/frontend/src/metabase/sharing/components/NewPulseSidebar.tsx b/frontend/src/metabase/sharing/components/NewPulseSidebar.tsx
index 3ed66590fc4d3a388a2d0090a5c37a97dcf1a8de..e6338bd7d9764470bc1ead9ffd58b9a08a9cce70 100644
--- a/frontend/src/metabase/sharing/components/NewPulseSidebar.tsx
+++ b/frontend/src/metabase/sharing/components/NewPulseSidebar.tsx
@@ -63,7 +63,11 @@ export function NewPulseSidebar({
             >
               {!emailConfigured &&
                 jt`You'll need to ${(
-                  <Link key="link" to="/admin/settings/email" className="link">
+                  <Link
+                    key="link"
+                    to="/admin/settings/email"
+                    className={CS.link}
+                  >
                     {t`set up email`}
                   </Link>
                 )} first.`}
@@ -101,7 +105,11 @@ export function NewPulseSidebar({
             >
               {!slackConfigured &&
                 jt`First, you'll have to ${(
-                  <Link key="link" to="/admin/settings/slack" className="link">
+                  <Link
+                    key="link"
+                    to="/admin/settings/slack"
+                    className={CS.link}
+                  >
                     {t`configure Slack`}
                   </Link>
                 )}.`}
diff --git a/frontend/test/metabase/lib/formatting.unit.spec.js b/frontend/test/metabase/lib/formatting.unit.spec.js
index f00b913b5abaa7b889faa53d0676dd3c66124c8a..ec4090fd8759c9426e32a4283104e243693c5e75 100644
--- a/frontend/test/metabase/lib/formatting.unit.spec.js
+++ b/frontend/test/metabase/lib/formatting.unit.spec.js
@@ -268,8 +268,8 @@ describe("formatting", () => {
       });
       // it's not actually a link
       expect(isElementOfType(formatted, ExternalLink)).toEqual(false);
-      // but it's formatted as a link
-      expect(formatted.props.className).toEqual("link link--wrappable");
+      // expect the text to be in a div (which has link formatting) rather than ExternalLink
+      expect(formatted.props["data-testid"]).toEqual("link-formatted-text");
     });
     it("should render image", () => {
       const formatted = formatValue("http://metabase.com/logo.png", {
@@ -498,8 +498,8 @@ describe("formatting", () => {
 
         // it is not a link set on the question level
         expect(isElementOfType(formatted, ExternalLink)).toEqual(false);
-        // it is formatted as a link cell for the dashboard level click behavior
-        expect(formatted.props.className).toEqual("link link--wrappable");
+        // expect the text to be in a div (which has link formatting) rather than ExternalLink
+        expect(formatted.props["data-testid"]).toEqual("link-formatted-text");
       });
     });