diff --git a/client/web/src/charts/components/pie-chart/PieChart.tsx b/client/web/src/charts/components/pie-chart/PieChart.tsx
index 03db6e511a64..0a1982c5b6cc 100644
--- a/client/web/src/charts/components/pie-chart/PieChart.tsx
+++ b/client/web/src/charts/components/pie-chart/PieChart.tsx
@@ -1,4 +1,4 @@
-import React, { ReactElement, useMemo, useState } from 'react'
+import React, { ReactElement, SVGProps, useMemo, useState } from 'react'
 
 import { Group } from '@visx/group'
 import Pie, { PieArcDatum } from '@visx/shape/lib/shapes/Pie'
@@ -25,7 +25,7 @@ function getSubtitle<Datum>(arc: PieArcDatum<Datum>, total: number): string {
     return `${((100 * arc.value) / total).toFixed(2)}%`
 }
 
-export interface PieChartProps<Datum> extends CategoricalLikeChart<Datum> {
+export interface PieChartProps<Datum> extends CategoricalLikeChart<Datum>, SVGProps<SVGSVGElement> {
     width: number
     height: number
     padding?: typeof DEFAULT_PADDING
@@ -40,8 +40,10 @@ export function PieChart<Datum>(props: PieChartProps<Datum>): ReactElement | nul
         getDatumValue,
         getDatumName,
         getDatumColor,
-        getDatumLink,
+        getDatumLink = noop,
         onDatumLinkClick = noop,
+        className,
+        ...attributes
     } = props
 
     // We have to track which arc is hovered to change order of rendering.
@@ -76,7 +78,13 @@ export function PieChart<Datum>(props: PieChartProps<Datum>): ReactElement | nul
     }
 
     return (
-        <svg aria-label="Pie chart" width={width} height={height} className={styles.svg}>
+        <svg
+            {...attributes}
+            aria-label="Pie chart"
+            width={width}
+            height={height}
+            className={classNames(styles.svg, className)}
+        >
             <Group top={centerY + padding.top} left={centerX + padding.left}>
                 <Pie data={sortedData} pieValue={getDatumValue} outerRadius={radius} cornerRadius={3}>
                     {pie => {
diff --git a/client/web/src/charts/types.ts b/client/web/src/charts/types.ts
index d8624008bdf2..b47a44e5b40a 100644
--- a/client/web/src/charts/types.ts
+++ b/client/web/src/charts/types.ts
@@ -18,7 +18,7 @@ export interface CategoricalLikeChart<Datum> {
     getDatumValue: (datum: Datum) => number
     getDatumName: (datum: Datum) => string
     getDatumColor: (datum: Datum) => string | undefined
-    getDatumLink: (datum: Datum) => string | undefined
+    getDatumLink?: (datum: Datum) => string | undefined | void
     onDatumLinkClick?: (event: React.MouseEvent) => void
 }
 
diff --git a/client/web/src/enterprise/insights/components/views/card/InsightCard.module.scss b/client/web/src/enterprise/insights/components/views/card/InsightCard.module.scss
new file mode 100644
index 000000000000..a9f07bf2fba6
--- /dev/null
+++ b/client/web/src/enterprise/insights/components/views/card/InsightCard.module.scss
@@ -0,0 +1,58 @@
+.view {
+    display: flex;
+    flex-direction: column;
+    padding: 1rem 1rem;
+    cursor: default;
+    outline: none;
+
+    &:focus,
+    &:focus-within {
+        box-shadow: var(--focus-box-shadow);
+    }
+}
+
+.header {
+    margin-bottom: 0.75rem;
+}
+
+.header-content {
+    display: flex;
+}
+
+.title {
+    // Truncation for multiple lines
+    // stylelint-disable-next-line value-no-vendor-prefix
+    display: -webkit-box;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 2;
+    margin-bottom: 0.25rem;
+    flex-grow: 1;
+}
+
+.action {
+    align-self: start;
+    display: flex;
+    align-items: center;
+    // Move icons in action panel visually closer to card border
+    margin-right: -0.5rem;
+    margin-top: -0.25rem;
+}
+
+.subtitle {
+    flex-basis: 100%;
+}
+
+.error-boundary {
+    padding-top: 0;
+}
+
+// Content states
+
+.loading-content {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
diff --git a/client/web/src/enterprise/insights/components/views/card/InsightCard.story.tsx b/client/web/src/enterprise/insights/components/views/card/InsightCard.story.tsx
new file mode 100644
index 000000000000..264aac6e3419
--- /dev/null
+++ b/client/web/src/enterprise/insights/components/views/card/InsightCard.story.tsx
@@ -0,0 +1,184 @@
+import React from 'react'
+
+import { Meta, Story } from '@storybook/react'
+import { noop } from 'lodash'
+import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon'
+import FilterOutlineIcon from 'mdi-react/FilterOutlineIcon'
+
+import { ErrorAlert } from '@sourcegraph/branded/src/components/alerts'
+import { Button, Menu, MenuButton, MenuItem, MenuList } from '@sourcegraph/wildcard'
+
+import { getLineColor, LegendItem, LegendList, ParentSize, Series } from '../../../../../charts'
+import { WebStory } from '../../../../../components/WebStory'
+import { SeriesChart } from '../chart'
+import { SeriesBasedChartTypes } from '../types'
+
+import * as Card from './InsightCard'
+
+export default {
+    title: 'web/insights/shared-components',
+    decorators: [story => <WebStory>{() => story()}</WebStory>],
+} as Meta
+
+export const InsightCardShowcase: Story = () => (
+    <main style={{ display: 'flex', flexWrap: 'wrap', gap: '1rem' }}>
+        <section>
+            <h2>Empty view</h2>
+            <Card.Root style={{ width: '400px', height: '400px' }}>
+                <Card.Header title="Empty card" />
+            </Card.Root>
+        </section>
+
+        <section>
+            <h2>View with loading content</h2>
+            <Card.Root style={{ width: '400px', height: '400px' }}>
+                <Card.Header title="Loading insight card" subtitle="View with loading content example" />
+                <Card.Loading>Loading insight</Card.Loading>
+            </Card.Root>
+        </section>
+
+        <section>
+            <h2>View with error-like content</h2>
+            <Card.Root style={{ width: '400px', height: '400px' }}>
+                <Card.Header title="Loading insight card" subtitle="View with errored content example" />
+                <ErrorAlert error={new Error("We couldn't find code insight")} />
+            </Card.Root>
+        </section>
+
+        <section>
+            <h2>Card with banner content (resizing state)</h2>
+            <Card.Root style={{ width: '400px', height: '400px' }}>
+                <Card.Header title="Resizing insight card" subtitle="Resizing insight card" />
+                <Card.Banner>Resizing</Card.Banner>
+            </Card.Root>
+        </section>
+
+        <section>
+            <h2>Card with insight chart</h2>
+            <InsightCardWithChart />
+        </section>
+
+        <section>
+            <h2>View with context action item</h2>
+            <Card.Root style={{ width: 400, height: 400 }}>
+                <Card.Header
+                    title="Chart view and looooooong loooooooooooooooong name of insight card block"
+                    subtitle="Subtitle chart description"
+                >
+                    <Button variant="icon" className="p-1">
+                        <FilterOutlineIcon size="1rem" />
+                    </Button>
+                    <Menu>
+                        <MenuButton variant="icon" className="p-1">
+                            <DotsVerticalIcon size={16} />
+                        </MenuButton>
+                        <MenuList>
+                            <MenuItem onSelect={noop}>Create</MenuItem>
+                            <MenuItem onSelect={noop}>Update</MenuItem>
+                            <MenuItem onSelect={noop}>Delete</MenuItem>
+                        </MenuList>
+                    </Menu>
+                </Card.Header>
+            </Card.Root>
+        </section>
+    </main>
+)
+
+interface StandardDatum {
+    a: number | null
+    b: number | null
+    c: number | null
+    x: number
+}
+
+const DATA: StandardDatum[] = [
+    {
+        x: 1588965700286 - 4 * 24 * 60 * 60 * 1000,
+        a: 4000,
+        b: 15000,
+        c: 5000,
+    },
+    {
+        x: 1588965700286 - 3 * 24 * 60 * 60 * 1000,
+        a: 4000,
+        b: 26000,
+        c: 5000,
+    },
+    {
+        x: 1588965700286 - 2 * 24 * 60 * 60 * 1000,
+        a: 5600,
+        b: 20000,
+        c: 5000,
+    },
+    {
+        x: 1588965700286 - 24 * 60 * 60 * 1000,
+        a: 9800,
+        b: 19000,
+        c: 5000,
+    },
+    {
+        x: 1588965700286,
+        a: 6000,
+        b: 17000,
+        c: 5000,
+    },
+]
+
+const SERIES: Series<StandardDatum>[] = [
+    {
+        dataKey: 'a',
+        name: 'A metric',
+        color: 'var(--blue)',
+    },
+    {
+        dataKey: 'b',
+        name: 'B metric',
+        color: 'var(--orange)',
+    },
+    {
+        dataKey: 'c',
+        name: 'C metric',
+        color: 'var(--indigo)',
+    },
+]
+
+const getXValue = (datum: { x: number }) => new Date(datum.x)
+
+function InsightCardWithChart() {
+    return (
+        <Card.Root style={{ width: '400px', height: '400px' }}>
+            <Card.Header title="Insight with chart" subtitle="CSS migration insight chart">
+                <Button variant="icon" className="p-1">
+                    <FilterOutlineIcon size="1rem" />
+                </Button>
+                <Menu>
+                    <MenuButton variant="icon" className="p-1">
+                        <DotsVerticalIcon size={16} />
+                    </MenuButton>
+                    <MenuList>
+                        <MenuItem onSelect={noop}>Create</MenuItem>
+                        <MenuItem onSelect={noop}>Update</MenuItem>
+                        <MenuItem onSelect={noop}>Delete</MenuItem>
+                    </MenuList>
+                </Menu>
+            </Card.Header>
+            <ParentSize>
+                {parent => (
+                    <SeriesChart
+                        type={SeriesBasedChartTypes.Line}
+                        data={DATA}
+                        series={SERIES}
+                        getXValue={getXValue}
+                        width={parent.width}
+                        height={parent.height}
+                    />
+                )}
+            </ParentSize>
+            <LegendList className="mt-3">
+                {SERIES.map(line => (
+                    <LegendItem key={line.dataKey.toString()} color={getLineColor(line)} name={line.name} />
+                ))}
+            </LegendList>
+        </Card.Root>
+    )
+}
diff --git a/client/web/src/enterprise/insights/components/views/card/InsightCard.tsx b/client/web/src/enterprise/insights/components/views/card/InsightCard.tsx
new file mode 100644
index 000000000000..58e39460c8ac
--- /dev/null
+++ b/client/web/src/enterprise/insights/components/views/card/InsightCard.tsx
@@ -0,0 +1,93 @@
+import React, { forwardRef, HTMLAttributes, ReactNode } from 'react'
+
+import classNames from 'classnames'
+import { useLocation } from 'react-router-dom'
+
+import { Card, ForwardReferenceComponent, LoadingSpinner } from '@sourcegraph/wildcard'
+
+import { ErrorBoundary } from '../../../../../components/ErrorBoundary'
+
+import styles from './InsightCard.module.scss'
+
+const InsightCard = forwardRef((props, reference) => {
+    const { title, children, className, as = 'section', ...otherProps } = props
+
+    return (
+        <Card as={as} tabIndex={0} ref={reference} {...otherProps} className={classNames(className, styles.view)}>
+            <ErrorBoundary location={useLocation()} className={styles.errorBoundary}>
+                {children}
+            </ErrorBoundary>
+        </Card>
+    )
+}) as ForwardReferenceComponent<'section'>
+
+interface InsightCardTitleProps {
+    title: string
+    subtitle?: string
+
+    /**
+     * It's primarily conceived as a slot for card actions (like filter buttons) that render
+     * element right after header text at the right top corner of the card.
+     */
+    children?: ReactNode
+}
+
+const InsightCardHeader = forwardRef((props, reference) => {
+    const { as: Component = 'header', title, subtitle, className, children, ...attributes } = props
+
+    return (
+        <Component {...attributes} ref={reference} className={classNames(styles.header, className)}>
+            <div className={styles.headerContent}>
+                <h4 title={title} className={styles.title}>
+                    {title}
+                </h4>
+
+                {children && (
+                    // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
+                    <div
+                        // View component usually is rendered within a view grid component. To suppress
+                        // bad click that lead to card DnD events in view grid we stop event bubbling for
+                        // clicks.
+                        onClick={event => event.stopPropagation()}
+                        onMouseDown={event => event.stopPropagation()}
+                        className={styles.action}
+                    >
+                        {children}
+                    </div>
+                )}
+            </div>
+
+            {subtitle && <div className={styles.subtitle}>{subtitle}</div>}
+        </Component>
+    )
+}) as ForwardReferenceComponent<'header', InsightCardTitleProps>
+
+const InsightCardLoading: React.FunctionComponent = props => (
+    <InsightCardBanner>
+        <LoadingSpinner />
+        {props.children}
+    </InsightCardBanner>
+)
+
+const InsightCardBanner: React.FunctionComponent<HTMLAttributes<HTMLDivElement>> = props => (
+    <div {...props} className={classNames(styles.loadingContent, props.className)}>
+        {props.children}
+    </div>
+)
+
+const Root = InsightCard
+const Header = InsightCardHeader
+const Loading = InsightCardLoading
+const Banner = InsightCardBanner
+
+export {
+    InsightCard,
+    InsightCardHeader,
+    InsightCardLoading,
+    InsightCardBanner,
+    // * as Card imports
+    Root,
+    Header,
+    Loading,
+    Banner,
+}
diff --git a/client/web/src/enterprise/insights/components/views/categorical/CategoricalChart.story.tsx b/client/web/src/enterprise/insights/components/views/chart/categorical/CategoricalChart.story.tsx
similarity index 92%
rename from client/web/src/enterprise/insights/components/views/categorical/CategoricalChart.story.tsx
rename to client/web/src/enterprise/insights/components/views/chart/categorical/CategoricalChart.story.tsx
index 64759e049d9f..09d6d01b20eb 100644
--- a/client/web/src/enterprise/insights/components/views/categorical/CategoricalChart.story.tsx
+++ b/client/web/src/enterprise/insights/components/views/chart/categorical/CategoricalChart.story.tsx
@@ -2,8 +2,8 @@ import React from 'react'
 
 import { Meta, Story } from '@storybook/react'
 
-import { WebStory } from '../../../../../components/WebStory'
-import { CategoricalBasedChartTypes } from '../types'
+import { WebStory } from '../../../../../../components/WebStory'
+import { CategoricalBasedChartTypes } from '../../types'
 
 import { CategoricalChart } from './CategoricalChart'
 
diff --git a/client/web/src/enterprise/insights/components/views/categorical/CategoricalChart.tsx b/client/web/src/enterprise/insights/components/views/chart/categorical/CategoricalChart.tsx
similarity index 67%
rename from client/web/src/enterprise/insights/components/views/categorical/CategoricalChart.tsx
rename to client/web/src/enterprise/insights/components/views/chart/categorical/CategoricalChart.tsx
index 6596c4c6d1fd..28286dab9724 100644
--- a/client/web/src/enterprise/insights/components/views/categorical/CategoricalChart.tsx
+++ b/client/web/src/enterprise/insights/components/views/chart/categorical/CategoricalChart.tsx
@@ -1,9 +1,9 @@
-import React from 'react'
+import React, { SVGProps } from 'react'
 
-import { CategoricalLikeChart, PieChart } from '../../../../../charts'
-import { CategoricalBasedChartTypes } from '../types'
+import { CategoricalLikeChart, PieChart } from '../../../../../../charts'
+import { CategoricalBasedChartTypes } from '../../types'
 
-interface CategoricalChartProps<Datum> extends CategoricalLikeChart<Datum> {
+interface CategoricalChartProps<Datum> extends CategoricalLikeChart<Datum>, Omit<SVGProps<SVGSVGElement>, 'type'> {
     type: CategoricalBasedChartTypes
     width: number
     height: number
diff --git a/client/web/src/enterprise/insights/components/views/chart/index.ts b/client/web/src/enterprise/insights/components/views/chart/index.ts
new file mode 100644
index 000000000000..d89f5e167132
--- /dev/null
+++ b/client/web/src/enterprise/insights/components/views/chart/index.ts
@@ -0,0 +1,2 @@
+export { SeriesChart } from './series/SeriesChart'
+export { CategoricalChart } from './categorical/CategoricalChart'
diff --git a/client/web/src/enterprise/insights/components/views/series/SeriesChart.story.tsx b/client/web/src/enterprise/insights/components/views/chart/series/SeriesChart.story.tsx
similarity index 93%
rename from client/web/src/enterprise/insights/components/views/series/SeriesChart.story.tsx
rename to client/web/src/enterprise/insights/components/views/chart/series/SeriesChart.story.tsx
index b0a3c45d2f79..9cce25abd790 100644
--- a/client/web/src/enterprise/insights/components/views/series/SeriesChart.story.tsx
+++ b/client/web/src/enterprise/insights/components/views/chart/series/SeriesChart.story.tsx
@@ -2,9 +2,9 @@ import React from 'react'
 
 import { Meta, Story } from '@storybook/react'
 
-import { Series } from '../../../../../charts'
-import { WebStory } from '../../../../../components/WebStory'
-import { SeriesBasedChartTypes } from '../types'
+import { Series } from '../../../../../../charts'
+import { WebStory } from '../../../../../../components/WebStory'
+import { SeriesBasedChartTypes } from '../../types'
 
 import { SeriesChart } from './SeriesChart'
 
diff --git a/client/web/src/enterprise/insights/components/views/series/SeriesChart.tsx b/client/web/src/enterprise/insights/components/views/chart/series/SeriesChart.tsx
similarity index 73%
rename from client/web/src/enterprise/insights/components/views/series/SeriesChart.tsx
rename to client/web/src/enterprise/insights/components/views/chart/series/SeriesChart.tsx
index 187dd39d0cb9..15545e01563f 100644
--- a/client/web/src/enterprise/insights/components/views/series/SeriesChart.tsx
+++ b/client/web/src/enterprise/insights/components/views/chart/series/SeriesChart.tsx
@@ -1,7 +1,7 @@
 import React from 'react'
 
-import { LineChart, SeriesLikeChart } from '../../../../../charts'
-import { SeriesBasedChartTypes } from '../types'
+import { LineChart, SeriesLikeChart } from '../../../../../../charts'
+import { SeriesBasedChartTypes } from '../../types'
 
 export interface SeriesChartProps<D> extends SeriesLikeChart<D> {
     type: SeriesBasedChartTypes
diff --git a/client/web/src/enterprise/insights/components/views/index.ts b/client/web/src/enterprise/insights/components/views/index.ts
new file mode 100644
index 000000000000..e82fe4c8c5e6
--- /dev/null
+++ b/client/web/src/enterprise/insights/components/views/index.ts
@@ -0,0 +1,4 @@
+export { InsightCard, InsightCardHeader, InsightCardBanner, InsightCardLoading } from './card/InsightCard'
+
+export { SeriesChart, CategoricalChart } from './chart'
+export { CategoricalBasedChartTypes, SeriesBasedChartTypes } from './types'
diff --git a/client/web/src/enterprise/insights/core/backend/code-insights-backend-context.ts b/client/web/src/enterprise/insights/core/backend/code-insights-backend-context.ts
index d1c262d1dcef..85990ecf4f83 100644
--- a/client/web/src/enterprise/insights/core/backend/code-insights-backend-context.ts
+++ b/client/web/src/enterprise/insights/core/backend/code-insights-backend-context.ts
@@ -1,10 +1,10 @@
 import React from 'react'
 
 import { throwError } from 'rxjs'
-import { LineChartContent, PieChartContent } from 'sourcegraph'
+import { LineChartContent } from 'sourcegraph'
 
 import { CodeInsightsBackend } from './code-insights-backend'
-import { RepositorySuggestionData } from './code-insights-backend-types'
+import { PieChartContent, RepositorySuggestionData } from './code-insights-backend-types'
 
 const errorMockMethod = (methodName: string) => () => throwError(new Error(`Implement ${methodName} method first`))
 
diff --git a/client/web/src/enterprise/insights/core/backend/code-insights-backend-types.ts b/client/web/src/enterprise/insights/core/backend/code-insights-backend-types.ts
index 6504e1a2446e..057dab51e3ff 100644
--- a/client/web/src/enterprise/insights/core/backend/code-insights-backend-types.ts
+++ b/client/web/src/enterprise/insights/core/backend/code-insights-backend-types.ts
@@ -8,8 +8,17 @@ import {
     CaptureGroupInsight,
     LangStatsInsight,
     InsightsDashboardOwner,
+    SearchBackendBasedInsight,
+    SearchRuntimeBasedInsight,
 } from '../types'
-import { SearchBackendBasedInsight, SearchRuntimeBasedInsight } from '../types/insight/types/search-insight'
+
+export interface PieChartContent<Datum> {
+    data: Datum[]
+    getDatumValue: (datum: Datum) => number
+    getDatumName: (datum: Datum) => string
+    getDatumColor: (datum: Datum) => string | undefined
+    getDatumLink?: (datum: Datum) => string | undefined
+}
 
 export interface DashboardCreateInput {
     name: string
diff --git a/client/web/src/enterprise/insights/core/backend/code-insights-backend.ts b/client/web/src/enterprise/insights/core/backend/code-insights-backend.ts
index 5fb8d005a437..e649619b51f7 100644
--- a/client/web/src/enterprise/insights/core/backend/code-insights-backend.ts
+++ b/client/web/src/enterprise/insights/core/backend/code-insights-backend.ts
@@ -1,5 +1,5 @@
 import { Observable } from 'rxjs'
-import { LineChartContent, PieChartContent } from 'sourcegraph'
+import { LineChartContent } from 'sourcegraph'
 
 import { ViewProviderResult } from '@sourcegraph/shared/src/api/extension/extensionHostApi'
 
@@ -23,6 +23,7 @@ import {
     AccessibleInsightInfo,
     RemoveInsightFromDashboardInput,
     RepositorySuggestionData,
+    PieChartContent,
 } from './code-insights-backend-types'
 
 export interface UiFeaturesConfig {
@@ -109,7 +110,7 @@ export interface CodeInsightsBackend {
     /**
      * Returns content for the code stats insight live preview chart.
      */
-    getLangStatsInsightContent: (input: GetLangStatsInsightContentInput) => Promise<PieChartContent<any>>
+    getLangStatsInsightContent: (input: GetLangStatsInsightContentInput) => Promise<PieChartContent<unknown>>
 
     getCaptureInsightContent: (input: CaptureInsightSettings) => Promise<LineChartContent<any, string>>
 
diff --git a/client/web/src/enterprise/insights/core/backend/gql-backend/code-insights-gql-backend.ts b/client/web/src/enterprise/insights/core/backend/gql-backend/code-insights-gql-backend.ts
index 5bb3827357ce..76648eb8e54d 100644
--- a/client/web/src/enterprise/insights/core/backend/gql-backend/code-insights-gql-backend.ts
+++ b/client/web/src/enterprise/insights/core/backend/gql-backend/code-insights-gql-backend.ts
@@ -1,7 +1,7 @@
 import { ApolloCache, ApolloClient, ApolloQueryResult, gql } from '@apollo/client'
 import { from, Observable, of } from 'rxjs'
 import { map, mapTo, switchMap } from 'rxjs/operators'
-import { LineChartContent, PieChartContent } from 'sourcegraph'
+import { LineChartContent } from 'sourcegraph'
 import {
     AddInsightViewToDashboardResult,
     DeleteDashboardResult,
@@ -35,6 +35,7 @@ import {
     InsightCreateInput,
     InsightUpdateInput,
     RemoveInsightFromDashboardInput,
+    PieChartContent,
 } from '../code-insights-backend-types'
 import { getRepositorySuggestions } from '../core/api/get-repository-suggestions'
 import { getResolvedSearchRepositories } from '../core/api/get-resolved-search-repositories'
@@ -230,7 +231,17 @@ export class CodeInsightsGqlBackend implements CodeInsightsBackend {
         getSearchInsightContent(input.insight)
 
     public getLangStatsInsightContent = (input: GetLangStatsInsightContentInput): Promise<PieChartContent<any>> =>
-        getLangStatsInsightContent(input.insight)
+        getLangStatsInsightContent(input.insight).then(data => {
+            const { data: dataList, dataKey, nameKey, fillKey = '', linkURLKey = '' } = data.pies[0]
+
+            return {
+                data: dataList,
+                getDatumValue: datum => datum[dataKey],
+                getDatumColor: datum => datum[fillKey ?? ''],
+                getDatumName: datum => datum[nameKey],
+                getDatumLink: datum => datum[linkURLKey],
+            }
+        })
 
     public getCaptureInsightContent = (input: CaptureInsightSettings): Promise<LineChartContent<any, string>> =>
         getCaptureGroupInsightsPreview(this.apolloClient, input)
diff --git a/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/LangStatsInsightLivePreview.module.scss b/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/LangStatsInsightLivePreview.module.scss
new file mode 100644
index 000000000000..533c4881b270
--- /dev/null
+++ b/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/LangStatsInsightLivePreview.module.scss
@@ -0,0 +1,32 @@
+.insight-card {
+    position: relative;
+    min-height: 20rem;
+}
+
+.chart-block {
+    flex: 1;
+}
+
+.chart-with-mock {
+    filter: blur(4px);
+    pointer-events: none;
+    opacity: 0.4;
+
+    // In order to turn off any interactions with chart like
+    // tooltip or chart shutter for user cursor we have to
+    // override pointer events. Since visx charts add pointer events
+    // by html attribute we have to use important statement.
+    :global(.visx-group) {
+        pointer-events: none !important;
+    }
+}
+
+.disable-banner {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    padding: 1rem 2rem;
+    text-align: center;
+}
diff --git a/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/LangStatsInsightLivePreview.tsx b/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/LangStatsInsightLivePreview.tsx
index 84ded9530809..4faf1d34c2bc 100644
--- a/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/LangStatsInsightLivePreview.tsx
+++ b/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/LangStatsInsightLivePreview.tsx
@@ -1,37 +1,46 @@
 import React from 'react'
 
-import { LivePreviewContainer } from '../../../../../../components/creation-ui-kit/live-preview-container/LivePreviewContainer'
-import { useDistinctValue } from '../../../../../../hooks/use-distinct-value'
+import classNames from 'classnames'
+import RefreshIcon from 'mdi-react/RefreshIcon'
+
+import { ErrorAlert } from '@sourcegraph/branded/src/components/alerts'
+import { isErrorLike } from '@sourcegraph/common'
+import { Button, useDeepMemo } from '@sourcegraph/wildcard'
+
+import { ParentSize } from '../../../../../../../../charts'
+import {
+    CategoricalBasedChartTypes,
+    CategoricalChart,
+    InsightCard,
+    InsightCardLoading,
+    InsightCardBanner,
+} from '../../../../../../components/views'
 
 import { useLangStatsPreviewContent } from './hooks/use-lang-stats-preview-content'
 import { DEFAULT_PREVIEW_MOCK } from './live-preview-mock-data'
 
-export interface LangStatsInsightLivePreviewProps {
-    /** Custom className for the root element of live preview. */
-    className?: string
-
-    /** List of repositories for insights. */
-    repository: string
-
-    /** Step value for cut off other small values. */
-    threshold: number
+import styles from './LangStatsInsightLivePreview.module.scss'
 
+export interface LangStatsInsightLivePreviewProps {
     /**
      * Disable prop to disable live preview.
      * Used in a consumer of this component when some required fields
      * for live preview are invalid.
      */
     disabled?: boolean
+    repository: string
+    threshold: number
+    className?: string
 }
 
 /**
  * Displays live preview chart for creation UI with latest insights settings
  * from creation UI form.
- * */
+ */
 export const LangStatsInsightLivePreview: React.FunctionComponent<LangStatsInsightLivePreviewProps> = props => {
     const { repository = '', threshold, disabled = false, className } = props
 
-    const previewSetting = useDistinctValue({
+    const previewSetting = useDeepMemo({
         repository: repository.trim(),
         otherThreshold: threshold / 100,
     })
@@ -39,16 +48,45 @@ export const LangStatsInsightLivePreview: React.FunctionComponent<LangStatsInsig
     const { loading, dataOrError, update } = useLangStatsPreviewContent({ disabled, previewSetting })
 
     return (
-        <LivePreviewContainer
-            dataOrError={dataOrError}
-            disabled={disabled}
-            className={className}
-            loading={loading}
-            defaultMock={DEFAULT_PREVIEW_MOCK}
-            onUpdateClick={update}
-            mockMessage={
-                <span>The chart preview will be shown here once you have filled out the repository field.</span>
-            }
-        />
+        <aside className={classNames(className)}>
+            <Button variant="icon" disabled={disabled} onClick={update}>
+                Live preview <RefreshIcon size="1rem" />
+            </Button>
+
+            <InsightCard className={styles.insightCard}>
+                {loading ? (
+                    <InsightCardLoading>Loading code insight</InsightCardLoading>
+                ) : isErrorLike(dataOrError) ? (
+                    <ErrorAlert error={dataOrError} />
+                ) : (
+                    <ParentSize className={styles.chartBlock}>
+                        {parent =>
+                            dataOrError ? (
+                                <CategoricalChart
+                                    type={CategoricalBasedChartTypes.Pie}
+                                    width={parent.width}
+                                    height={parent.height}
+                                    {...dataOrError}
+                                />
+                            ) : (
+                                <>
+                                    <CategoricalChart
+                                        type={CategoricalBasedChartTypes.Pie}
+                                        width={parent.width}
+                                        height={parent.height}
+                                        className={styles.chartWithMock}
+                                        {...DEFAULT_PREVIEW_MOCK}
+                                    />
+                                    <InsightCardBanner className={styles.disableBanner}>
+                                        The chart preview will be shown here once you have filled out the repository
+                                        field.
+                                    </InsightCardBanner>
+                                </>
+                            )
+                        }
+                    </ParentSize>
+                )}
+            </InsightCard>
+        </aside>
     )
 }
diff --git a/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/hooks/use-lang-stats-preview-content.ts b/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/hooks/use-lang-stats-preview-content.ts
index 1faca37ab6b8..10db614c0e97 100644
--- a/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/hooks/use-lang-stats-preview-content.ts
+++ b/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/hooks/use-lang-stats-preview-content.ts
@@ -1,11 +1,10 @@
 import { useContext, useEffect, useState } from 'react'
 
-import { PieChartContent } from 'sourcegraph'
-
 import { asError } from '@sourcegraph/common'
 import { useDebounce } from '@sourcegraph/wildcard'
 
 import { CodeInsightsBackendContext } from '../../../../../../../core/backend/code-insights-backend-context'
+import { PieChartContent } from '../../../../../../../core/backend/code-insights-backend-types'
 
 export interface UseLangStatsPreviewContentProps {
     /** Prop to turn off fetching and reset data for live preview chart. */
@@ -19,13 +18,13 @@ export interface UseLangStatsPreviewContentProps {
 
 export interface UseLangStatsPreviewContentAPI {
     loading: boolean
-    dataOrError: PieChartContent<any> | Error | undefined
+    dataOrError: PieChartContent<object> | Error | undefined
     update: () => void
 }
 
 /**
  * Unified logic for fetching data for live preview chart of lang stats insight.
- * */
+ */
 export function useLangStatsPreviewContent(props: UseLangStatsPreviewContentProps): UseLangStatsPreviewContentAPI {
     const { disabled, previewSetting } = props
 
diff --git a/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/live-preview-mock-data.ts b/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/live-preview-mock-data.ts
index 71ad5ea1724f..6a7ff95155d5 100644
--- a/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/live-preview-mock-data.ts
+++ b/client/web/src/enterprise/insights/pages/insights/creation/lang-stats/components/live-preview-chart/live-preview-mock-data.ts
@@ -1,59 +1,50 @@
 import { random } from 'lodash'
-import { PieChartContent } from 'sourcegraph'
 
-export const DEFAULT_PREVIEW_MOCK: PieChartContent<any> = {
-    chart: 'pie' as const,
-    pies: [
+import { PieChartContent } from '../../../../../../core/backend/code-insights-backend-types'
+
+interface PreviewDatum {
+    name: string
+    value: number
+    fill: string
+}
+
+export const DEFAULT_PREVIEW_MOCK: PieChartContent<PreviewDatum> = {
+    data: [
         {
-            dataKey: 'value',
-            nameKey: 'name',
-            fillKey: 'fill',
-            linkURLKey: 'linkURL',
-            data: [
-                {
-                    name: 'Covered',
-                    value: 0.3,
-                    fill: 'var(--oc-grape-7)',
-                    linkURL: '#Covered',
-                },
-                {
-                    name: 'Not covered',
-                    value: 0.7,
-                    fill: 'var(--oc-orange-7)',
-                    linkURL: '#Not_covered',
-                },
-            ],
+            name: 'Covered',
+            value: 0.3,
+            fill: 'var(--oc-grape-7)',
+        },
+        {
+            name: 'Not covered',
+            value: 0.7,
+            fill: 'var(--oc-orange-7)',
         },
     ],
+    getDatumName: datum => datum.name,
+    getDatumColor: datum => datum.fill,
+    getDatumValue: datum => datum.value,
 }
 
-export function getRandomLangStatsMock(): PieChartContent<any> {
+export function getRandomLangStatsMock(): PieChartContent<PreviewDatum> {
     const randomFirstPieValue = random(0, 0.6)
     const randomSecondPieValue = 1 - randomFirstPieValue
 
     return {
-        chart: 'pie' as const,
-        pies: [
+        data: [
+            {
+                name: 'JavaScript',
+                value: randomFirstPieValue,
+                fill: 'var(--oc-grape-7)',
+            },
             {
-                dataKey: 'value',
-                nameKey: 'name',
-                fillKey: 'fill',
-                linkURLKey: 'linkURL',
-                data: [
-                    {
-                        name: 'JavaScript',
-                        value: randomFirstPieValue,
-                        fill: 'var(--oc-grape-7)',
-                        linkURL: '#Covered',
-                    },
-                    {
-                        name: 'Typescript',
-                        value: randomSecondPieValue,
-                        fill: 'var(--oc-orange-7)',
-                        linkURL: '#Not_covered',
-                    },
-                ],
+                name: 'Typescript',
+                value: randomSecondPieValue,
+                fill: 'var(--oc-orange-7)',
             },
         ],
+        getDatumName: datum => datum.name,
+        getDatumColor: datum => datum.fill,
+        getDatumValue: datum => datum.value,
     }
 }
diff --git a/client/web/src/views/components/view/content/chart-view-content/charts/MaybeLink.tsx b/client/web/src/views/components/view/content/chart-view-content/charts/MaybeLink.tsx
index 298278217cad..0d45614e2b51 100644
--- a/client/web/src/views/components/view/content/chart-view-content/charts/MaybeLink.tsx
+++ b/client/web/src/views/components/view/content/chart-view-content/charts/MaybeLink.tsx
@@ -3,7 +3,7 @@ import React from 'react'
 import { Link } from '@sourcegraph/wildcard'
 
 interface MaybeLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
-    to?: string
+    to?: string | void
 }
 
 /** Wraps the children in a link if to (link href) prop is passed. */