diff --git a/packages/demo/src/content/components/format-date.mdx b/packages/demo/src/content/components/format-date.mdx
index f82eb53..c654c97 100644
--- a/packages/demo/src/content/components/format-date.mdx
+++ b/packages/demo/src/content/components/format-date.mdx
@@ -18,6 +18,8 @@ The FormatDate component renders a date as either **relative** time
When showing relative time, a tooltip reveals the absolute time on hover or keyboard focus.
+If no date is provided (`null`, `undefined`, or an empty string), the component renders a `---` placeholder instead.
+
## Time zones
Absolute time is formatted in **UTC by default**. To render in a different zone, pass a `timeZone` (e.g. `"America/New_York"`). Relative time reads the same everywhere.
@@ -108,14 +110,26 @@ For a short numeric date like `2026-06-09`, use numeric options. Digit _order_ i
/>
```
+## Empty state
+
+When `date` is missing — `null`, `undefined`, or an empty string — the component renders a `---` placeholder with an accessible "No date" label. This is distinct from passing a present-but-unparseable value (e.g. `"not-a-date"`), which renders `Invalid date`.
+
+
+
+```tsx
+
+
+
+```
+
## Props
-| Name | Description | Type | Default | Required |
-| ----------------- | ----------------------------------------------------------------------- | ---------------------------- | ---------- | -------- |
-| `date` | The date to display | `string`, `number`, `Date` | — | ✅ |
-| `displayAs` | Render relative or absolute time | `relative`, `absolute` | `absolute` | ❌ |
-| `timeZone` | Time zone used for absolute formatting | `string` | `UTC` | ❌ |
-| `locale` | BCP 47 locale used for formatting | `string` | `en-US` | ❌ |
-| `tooltip` | When relative, show a tooltip with the absolute time on hover/focus | `boolean` | `true` | ❌ |
-| `live` | When relative, re-render on an interval so the value stays current | `boolean` | `true` | ❌ |
-| `absoluteOptions` | Override the `Intl.DateTimeFormat` options used for absolute formatting | `Intl.DateTimeFormatOptions` | — | ❌ |
+| Name | Description | Type | Default | Required |
+| ----------------- | ----------------------------------------------------------------------- | ---------------------------------- | ---------- | -------- |
+| `date` | The date to display | `string`, `number`, `Date`, `null` | — | ✅ |
+| `displayAs` | Render relative or absolute time | `relative`, `absolute` | `absolute` | ❌ |
+| `timeZone` | Time zone used for absolute formatting | `string` | `UTC` | ❌ |
+| `locale` | BCP 47 locale used for formatting | `string` | `en-US` | ❌ |
+| `tooltip` | When relative, show a tooltip with the absolute time on hover/focus | `boolean` | `true` | ❌ |
+| `live` | When relative, re-render on an interval so the value stays current | `boolean` | `true` | ❌ |
+| `absoluteOptions` | Override the `Intl.DateTimeFormat` options used for absolute formatting | `Intl.DateTimeFormatOptions` | — | ❌ |
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 611a454..2c35996 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -2,7 +2,7 @@
"name": "@eqtylab/equality",
"description": "EQTYLab's component and token-based design system",
"homepage": "https://equality.eqtylab.io/",
- "version": "2.1.2",
+ "version": "2.1.3",
"license": "Apache-2.0",
"keywords": [
"component library",
diff --git a/packages/ui/src/components/format-date/format-date.tsx b/packages/ui/src/components/format-date/format-date.tsx
index 0b286ab..d5296d1 100644
--- a/packages/ui/src/components/format-date/format-date.tsx
+++ b/packages/ui/src/components/format-date/format-date.tsx
@@ -15,8 +15,8 @@ export interface FormatDateProps extends Omit<
React.TimeHTMLAttributes,
'dateTime' | 'children'
> {
- /** The date to display. Accepts an ISO 8601 string, epoch milliseconds, or a Date. */
- date: string | number | Date;
+ /** The date to display. Accepts an ISO 8601 string, epoch milliseconds, or a Date. A missing value (null, undefined, or empty string) renders a placeholder. */
+ date: string | number | Date | null | undefined;
/** Render relative ("2 weeks ago") or absolute ("Jun 9 2026, 18:42:03 UTC") time. */
displayAs?: FormatDateDisplayMode;
/** Time zone used for absolute formatting. Defaults to "UTC". */
@@ -58,7 +58,16 @@ const RELATIVE_DIVISIONS: { amount: number; unit: Intl.RelativeTimeFormatUnit }[
// Re-render relative time so values like "Just now" stay accurate without busy-looping
const LIVE_INTERVAL_MS = 30_000;
-// Parse any date-like input into a Date, or null if missing/invalid
+// Shown when no date is provided, as distinct from a date that fails to parse
+const NO_DATE_PLACEHOLDER = '---';
+
+// A missing value (null, undefined, or empty/whitespace string) is "no date",
+// as opposed to a present-but-unparseable value, which is "Invalid date"
+function isMissing(value: string | number | Date | null | undefined): value is null | undefined {
+ return value == null || (typeof value === 'string' && value.trim() === '');
+}
+
+// Parse any date-like input into a Date, or null if invalid
function toDate(value: string | number | Date): Date | null {
const date = value instanceof Date ? value : new Date(value);
return Number.isNaN(date.getTime()) ? null : date;
@@ -98,7 +107,8 @@ function FormatDate({
className,
...props
}: FormatDateProps) {
- const parsed = React.useMemo(() => toDate(date), [date]);
+ const missing = isMissing(date);
+ const parsed = React.useMemo(() => (isMissing(date) ? null : toDate(date)), [date]);
const [now, setNow] = React.useState(() => new Date());
const [mounted, setMounted] = React.useState(false);
@@ -112,11 +122,19 @@ function FormatDate({
return () => clearInterval(id);
}, [isRelative, live]);
+ if (missing) {
+ return (
+
+ {NO_DATE_PLACEHOLDER}
+
+ );
+ }
+
if (!parsed) {
return (
-
+
);
}