diff --git a/packages/desktop-client/src/components/Notes.tsx b/packages/desktop-client/src/components/Notes.tsx
index 4e8e4b7243f7dd12f9287150b186785a94f88acf..06dd4c5ff31d63552a8a586b0321d0a00cde7de7 100644
--- a/packages/desktop-client/src/components/Notes.tsx
+++ b/packages/desktop-client/src/components/Notes.tsx
@@ -75,6 +75,9 @@ const markdownStyles = css({
   '& td': {
     padding: '0.25rem 0.75rem',
   },
+  '& h3': {
+    fontSize: 15,
+  },
 });
 
 type NotesProps = {
diff --git a/packages/desktop-client/src/components/reports/Overview.tsx b/packages/desktop-client/src/components/reports/Overview.tsx
index faec350ce6fa49e15b3b89c6625a03eee29f7c58..c775077666f51b99ac7cb798ff2f43c69f73d833 100644
--- a/packages/desktop-client/src/components/reports/Overview.tsx
+++ b/packages/desktop-client/src/components/reports/Overview.tsx
@@ -15,6 +15,7 @@ import { send } from 'loot-core/src/platform/client/fetch';
 import {
   type CustomReportWidget,
   type ExportImportDashboard,
+  type MarkdownWidget,
   type Widget,
 } from 'loot-core/src/types/models';
 
@@ -35,6 +36,7 @@ import { NON_DRAGGABLE_AREA_CLASS_NAME } from './constants';
 import { LoadingIndicator } from './LoadingIndicator';
 import { CashFlowCard } from './reports/CashFlowCard';
 import { CustomReportListCards } from './reports/CustomReportListCards';
+import { MarkdownCard } from './reports/MarkdownCard';
 import { NetWorthCard } from './reports/NetWorthCard';
 import { SpendingCard } from './reports/SpendingCard';
 
@@ -46,22 +48,6 @@ function isCustomReportWidget(widget: Widget): widget is CustomReportWidget {
   return widget.type === 'custom-report';
 }
 
-type LayoutWidget = Layout & Pick<Widget, 'type' | 'meta'>;
-
-function useWidgetLayout(widgets: Widget[]): LayoutWidget[] {
-  return widgets.map(widget => ({
-    i: widget.id,
-    type: widget.type,
-    x: widget.x,
-    y: widget.y,
-    w: widget.width,
-    h: widget.height,
-    minW: isCustomReportWidget(widget) ? 2 : 3,
-    minH: isCustomReportWidget(widget) ? 1 : 2,
-    meta: widget.meta,
-  }));
-}
-
 export function Overview() {
   const { t } = useTranslation();
   const dispatch = useDispatch();
@@ -96,7 +82,17 @@ export function Overview() {
   const isDashboardsFeatureEnabled = useFeatureFlag('dashboards');
   const spendingReportFeatureFlag = useFeatureFlag('spendingReport');
 
-  const baseLayout = useWidgetLayout(widgets);
+  const baseLayout = widgets.map(widget => ({
+    i: widget.id,
+    w: widget.width,
+    h: widget.height,
+    minW:
+      isCustomReportWidget(widget) || widget.type === 'markdown-card' ? 2 : 3,
+    minH:
+      isCustomReportWidget(widget) || widget.type === 'markdown-card' ? 1 : 2,
+    ...widget,
+  }));
+
   const layout =
     spendingReportFeatureFlag &&
     !isDashboardsFeatureEnabled &&
@@ -308,10 +304,7 @@ export function Overview() {
     );
   };
 
-  const onMetaChange = <T extends LayoutWidget>(
-    widget: T,
-    newMeta: T['meta'],
-  ) => {
+  const onMetaChange = (widget: { i: string }, newMeta: Widget['meta']) => {
     send('dashboard-update-widget', {
       id: widget.i,
       meta: newMeta,
@@ -384,7 +377,18 @@ export function Overview() {
                             }
                             if (isExistingCustomReport(item)) {
                               const [, reportId] = item.split('custom-report-');
-                              onAddWidget('custom-report', { id: reportId });
+                              onAddWidget<CustomReportWidget>('custom-report', {
+                                id: reportId,
+                              });
+                              return;
+                            }
+
+                            if (item === 'markdown-card') {
+                              onAddWidget<MarkdownWidget>(item, {
+                                content: t(
+                                  '### Text Widget\n\nEdit this widget to change the **markdown** content.',
+                                ),
+                              });
                               return;
                             }
 
@@ -407,6 +411,10 @@ export function Overview() {
                                   },
                                 ]
                               : []),
+                            {
+                              name: 'markdown-card' as const,
+                              text: t('Text widget'),
+                            },
                             {
                               name: 'custom-report' as const,
                               text: t('New custom report'),
@@ -522,32 +530,35 @@ export function Overview() {
                   <NetWorthCard
                     isEditing={isEditing}
                     accounts={accounts}
-                    meta={item.meta && 'name' in item.meta ? item.meta : {}}
+                    meta={item.meta}
                     onMetaChange={newMeta => onMetaChange(item, newMeta)}
                     onRemove={() => onRemoveWidget(item.i)}
                   />
                 ) : item.type === 'cash-flow-card' ? (
                   <CashFlowCard
                     isEditing={isEditing}
-                    meta={item.meta && 'name' in item.meta ? item.meta : {}}
+                    meta={item.meta}
                     onMetaChange={newMeta => onMetaChange(item, newMeta)}
                     onRemove={() => onRemoveWidget(item.i)}
                   />
                 ) : item.type === 'spending-card' ? (
                   <SpendingCard
                     isEditing={isEditing}
-                    meta={item.meta && 'name' in item.meta ? item.meta : {}}
+                    meta={item.meta}
+                    onMetaChange={newMeta => onMetaChange(item, newMeta)}
+                    onRemove={() => onRemoveWidget(item.i)}
+                  />
+                ) : item.type === 'markdown-card' ? (
+                  <MarkdownCard
+                    isEditing={isEditing}
+                    meta={item.meta}
                     onMetaChange={newMeta => onMetaChange(item, newMeta)}
                     onRemove={() => onRemoveWidget(item.i)}
                   />
                 ) : item.type === 'custom-report' ? (
                   <CustomReportListCards
                     isEditing={isEditing}
-                    report={
-                      item.meta && 'id' in item.meta
-                        ? customReportMap.get(item.meta.id)
-                        : undefined
-                    }
+                    report={customReportMap.get(item.meta.id)}
                     onRemove={() => onRemoveWidget(item.i)}
                   />
                 ) : null}
diff --git a/packages/desktop-client/src/components/reports/ReportCardName.tsx b/packages/desktop-client/src/components/reports/ReportCardName.tsx
index 3eac1ece293f46a8afbc55083793be8a9d8e7504..670ac0a99874e90ba7e328ee68332a27b0b1cf87 100644
--- a/packages/desktop-client/src/components/reports/ReportCardName.tsx
+++ b/packages/desktop-client/src/components/reports/ReportCardName.tsx
@@ -30,8 +30,7 @@ export const ReportCardName = ({
           onUpdate={onChange}
           onEscape={onClose}
           style={{
-            fontSize: 15,
-            fontWeight: 500,
+            ...styles.mediumText,
             marginTop: -6,
             marginBottom: -1,
             marginLeft: -6,
@@ -46,7 +45,6 @@ export const ReportCardName = ({
     <Block
       style={{
         ...styles.mediumText,
-        fontWeight: 500,
         marginBottom: 5,
       }}
       role="heading"
diff --git a/packages/desktop-client/src/components/reports/reports/MarkdownCard.tsx b/packages/desktop-client/src/components/reports/reports/MarkdownCard.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1ec90260e821cef21c55763c90792b6336de9b4c
--- /dev/null
+++ b/packages/desktop-client/src/components/reports/reports/MarkdownCard.tsx
@@ -0,0 +1,141 @@
+import React, { useState } from 'react';
+import { TextArea } from 'react-aria-components';
+import { useTranslation } from 'react-i18next';
+import ReactMarkdown from 'react-markdown';
+
+import { css } from 'glamor';
+
+import { type MarkdownWidget } from 'loot-core/src/types/models';
+
+import { styles, theme } from '../../../style';
+import { Menu } from '../../common/Menu';
+import { Text } from '../../common/Text';
+import { View } from '../../common/View';
+import { NON_DRAGGABLE_AREA_CLASS_NAME } from '../constants';
+import { ReportCard } from '../ReportCard';
+
+const markdownStyles = css({
+  paddingRight: 20,
+  '& h3': styles.mediumText,
+});
+
+type MarkdownCardProps = {
+  isEditing?: boolean;
+  meta: MarkdownWidget['meta'];
+  onMetaChange: (newMeta: MarkdownWidget['meta']) => void;
+  onRemove: () => void;
+};
+
+export function MarkdownCard({
+  isEditing,
+  meta,
+  onMetaChange,
+  onRemove,
+}: MarkdownCardProps) {
+  const { t } = useTranslation();
+
+  const [isVisibleTextArea, setIsVisibleTextArea] = useState(false);
+
+  return (
+    <ReportCard
+      isEditing={isEditing}
+      menuItems={[
+        {
+          type: Menu.label,
+          name: t('Text position:'),
+          text: '',
+        },
+        {
+          name: 'text-left',
+          text: t('Left'),
+        },
+        {
+          name: 'text-center',
+          text: t('Center'),
+        },
+        {
+          name: 'text-right',
+          text: t('Right'),
+        },
+        Menu.line,
+        {
+          name: 'edit',
+          text: t('Edit content'),
+        },
+        {
+          name: 'remove',
+          text: t('Remove'),
+        },
+      ]}
+      onMenuSelect={item => {
+        switch (item) {
+          case 'text-left':
+            onMetaChange({
+              ...meta,
+              text_align: 'left',
+            });
+            break;
+          case 'text-center':
+            onMetaChange({
+              ...meta,
+              text_align: 'center',
+            });
+            break;
+          case 'text-right':
+            onMetaChange({
+              ...meta,
+              text_align: 'right',
+            });
+            break;
+          case 'edit':
+            setIsVisibleTextArea(true);
+            break;
+          case 'remove':
+            onRemove();
+            break;
+          default:
+            throw new Error(`Unrecognized selection: ${item}`);
+        }
+      }}
+    >
+      <View
+        style={{
+          flex: 1,
+          paddingTop: 5,
+          paddingLeft: 20,
+          overflowY: 'auto',
+          height: '100%',
+          textAlign: meta.text_align,
+        }}
+      >
+        {isVisibleTextArea ? (
+          <TextArea
+            style={{
+              height: '100%',
+              border: 0,
+              marginTop: 11,
+              marginBottom: 11,
+              marginRight: 20,
+              color: theme.formInputText,
+              backgroundColor: theme.tableBackground,
+            }}
+            className={NON_DRAGGABLE_AREA_CLASS_NAME}
+            autoFocus
+            defaultValue={meta.content}
+            onBlur={event => {
+              onMetaChange({
+                ...meta,
+                content: event.currentTarget.value,
+              });
+              setIsVisibleTextArea(false);
+            }}
+          />
+        ) : (
+          <Text {...markdownStyles}>
+            <ReactMarkdown linkTarget="_blank">{meta.content}</ReactMarkdown>
+          </Text>
+        )}
+      </View>
+    </ReportCard>
+  );
+}
diff --git a/packages/loot-core/src/server/dashboard/app.ts b/packages/loot-core/src/server/dashboard/app.ts
index edb825eee7ba180f49e700be6f99cb30b84fc91b..2fe1093e8ff4213851fb6e37d3354bd329e32206 100644
--- a/packages/loot-core/src/server/dashboard/app.ts
+++ b/packages/loot-core/src/server/dashboard/app.ts
@@ -80,6 +80,7 @@ const exportModel = {
           'cash-flow-card',
           'spending-card',
           'custom-report',
+          'markdown-card',
         ].includes(widget.type)
       ) {
         throw new ValidationError(
diff --git a/packages/loot-core/src/types/models/dashboard.d.ts b/packages/loot-core/src/types/models/dashboard.d.ts
index 2d0547326715511895e6f1d1444ec6d8a3747cf0..6dc20360f4aac4ed6d2656c1e40b1477b8b248e3 100644
--- a/packages/loot-core/src/types/models/dashboard.d.ts
+++ b/packages/loot-core/src/types/models/dashboard.d.ts
@@ -30,8 +30,16 @@ export type CustomReportWidget = AbstractWidget<
   'custom-report',
   { id: string }
 >;
+export type MarkdownWidget = AbstractWidget<
+  'markdown-card',
+  { content: string; text_align?: 'left' | 'right' | 'center' }
+>;
 
-type SpecializedWidget = NetWorthWidget | CashFlowWidget | SpendingWidget;
+type SpecializedWidget =
+  | NetWorthWidget
+  | CashFlowWidget
+  | SpendingWidget
+  | MarkdownWidget;
 export type Widget = SpecializedWidget | CustomReportWidget;
 export type NewWidget = Omit<Widget, 'id' | 'tombstone'>;
 
diff --git a/upcoming-release-notes/3288.md b/upcoming-release-notes/3288.md
new file mode 100644
index 0000000000000000000000000000000000000000..5444e81af19e7b8c4860b30d86f0efd079ef2329
--- /dev/null
+++ b/upcoming-release-notes/3288.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [Matissjanis]
+---
+
+Dashboards: text widget support.