diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource-tile/index.ts b/apps/sim/app/workspace/[workspaceId]/components/resource-tile/index.ts
index f078177c862..b418364123e 100644
--- a/apps/sim/app/workspace/[workspaceId]/components/resource-tile/index.ts
+++ b/apps/sim/app/workspace/[workspaceId]/components/resource-tile/index.ts
@@ -1,7 +1,2 @@
-export {
- BrandTile,
- RESOURCE_TILE_BASE,
- RESOURCE_TILE_FILL,
- RESOURCE_TILE_PLAIN,
- ResourceTile,
-} from '@/app/workspace/[workspaceId]/components/resource-tile/resource-tile'
+export { RESOURCE_TILE_BASE, RESOURCE_TILE_PLAIN, ResourceTile } from '@sim/emcn'
+export { BrandTile } from './resource-tile'
diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource-tile/resource-tile.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource-tile/resource-tile.tsx
index 4bd7a0a773e..7b71adeffc3 100644
--- a/apps/sim/app/workspace/[workspaceId]/components/resource-tile/resource-tile.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/components/resource-tile/resource-tile.tsx
@@ -1,38 +1,7 @@
import type { ComponentType } from 'react'
-import { cn } from '@sim/emcn'
+import { cn, RESOURCE_TILE_BASE, RESOURCE_TILE_PLAIN } from '@sim/emcn'
import { getTileIconColorClass } from '@/blocks/icon-color'
-interface ResourceTileProps {
- icon: ComponentType<{ className?: string }>
-}
-
-/**
- * Geometry and border of the square resource tile — the single source for that
- * chrome, shared by {@link ResourceTile} and `SettingsResourceRow` so the skills,
- * custom tools, and settings surfaces cannot drift apart. Pair with a fill. Sizing
- * the glyph is the tile's job: the descendant rule outranks an icon's own class.
- */
-export const RESOURCE_TILE_BASE =
- 'flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-xl border border-[var(--border-1)] [&_svg]:size-5'
-
-/** Filled treatment worn by the skills and custom tools resource tiles. */
-export const RESOURCE_TILE_FILL = 'bg-[var(--surface-4)] dark:bg-[var(--surface-5)]'
-
-/** Page-background fill, for tiles holding a brand logo or a site favicon. */
-export const RESOURCE_TILE_PLAIN = 'bg-[var(--bg)]'
-
-/**
- * Square glyph tile identifying a workspace resource — the leading visual on a
- * resource's row and on its detail heading.
- */
-export function ResourceTile({ icon: Icon }: ResourceTileProps) {
- return (
-
-
-
- )
-}
-
interface BrandTileProps {
icon: ComponentType<{ className?: string }>
background: string | null | undefined
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/browser/components/passwords-view/passwords-view.test.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/browser/components/passwords-view/passwords-view.test.tsx
index 293b51e46a4..65218a0fa28 100644
--- a/apps/sim/app/workspace/[workspaceId]/settings/components/browser/components/passwords-view/passwords-view.test.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/settings/components/browser/components/passwords-view/passwords-view.test.tsx
@@ -16,8 +16,41 @@ const { mockBridge, mockSearch, mockToast } = vi.hoisted(() => ({
}))
vi.mock('@sim/emcn', () => ({
- /** `SettingsResourceRow` composes its tile classes with `cn`. */
cn: (...classes: unknown[]) => classes.filter(Boolean).join(' '),
+ ResourceRow: ({
+ title,
+ description,
+ icon,
+ trailing,
+ onClick,
+ clickLabel,
+ disabled,
+ }: {
+ title: ReactNode
+ description?: ReactNode
+ icon?: ReactNode
+ trailing?: ReactNode
+ onClick?: () => void
+ clickLabel?: string
+ disabled?: boolean
+ }) => (
+
+ {onClick && !disabled ? (
+
+ ) : (
+
+ {icon}
+ {title}
+ {description}
+
+ )}
+ {trailing}
+
+ ),
ArrowLeft: () => ,
ArrowRight: () => ,
ChipConfirmModal: ({
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/index.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/index.ts
index 335ecd0a0cb..dfadf29c7c1 100644
--- a/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/index.ts
+++ b/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/index.ts
@@ -1,6 +1,2 @@
-export {
- RESOURCE_LIST_GRID,
- RESOURCE_LIST_STACK,
- RESOURCE_ROW_ARROW_CLASSES,
- SettingsResourceRow,
-} from './settings-resource-row'
+export { RESOURCE_LIST_GRID, RESOURCE_LIST_STACK, RESOURCE_ROW_ARROW_CLASSES } from '@sim/emcn'
+export { SettingsResourceRow } from './settings-resource-row'
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row.test.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row.test.tsx
new file mode 100644
index 00000000000..1814ff42070
--- /dev/null
+++ b/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row.test.tsx
@@ -0,0 +1,31 @@
+/**
+ * @vitest-environment jsdom
+ */
+import { renderToStaticMarkup } from 'react-dom/server'
+import { describe, expect, it } from 'vitest'
+import { SettingsResourceRow } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row'
+
+describe('SettingsResourceRow', () => {
+ it('renders a real Next link beside interactive trailing controls', () => {
+ const html = renderToStaticMarkup(
+ More actions}
+ />
+ )
+ const container = document.createElement('div')
+ container.innerHTML = html
+
+ const link = container.querySelector('a')
+ const action = container.querySelector('button')
+ expect(link?.getAttribute('href')).toBe('/workspace/example/knowledge')
+ expect(link?.getAttribute('aria-label')).toBe('Open knowledge base')
+ expect(link?.getAttribute('aria-describedby')).toBe(container.querySelector('span[id]')?.id)
+ expect(link?.contains(action ?? null)).toBe(false)
+ expect(action?.textContent).toBe('More actions')
+ })
+})
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row.tsx
index e0c7ddbc74b..73fa76dc966 100644
--- a/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row.tsx
@@ -1,255 +1,24 @@
-import { type ReactNode, useId } from 'react'
-import { cn, OverflowText } from '@sim/emcn'
-import { ArrowRight } from '@sim/emcn/icons'
+import type { ComponentProps } from 'react'
+import { ResourceRow, type ResourceRowProps } from '@sim/emcn'
import Link from 'next/link'
-import {
- RESOURCE_TILE_BASE,
- RESOURCE_TILE_FILL,
- RESOURCE_TILE_PLAIN,
-} from '@/app/workspace/[workspaceId]/components/resource-tile'
-/**
- * The canonical settings "resource row": a rounded-bordered icon tile, a
- * title + muted description text block, and an optional trailing slot
- * (action chips, a {@link RowActionsMenu}, a status label, etc.).
- *
- * Single source of truth for the credential-style row shared by the BYOK key
- * manager and recently-deleted lists — never re-derive the
- * tile/text chrome per consumer. The tile force-sizes any `