diff --git a/.changeset/typography-scale-headings.md b/.changeset/typography-scale-headings.md
new file mode 100644
index 0000000000..e3b3fc2567
--- /dev/null
+++ b/.changeset/typography-scale-headings.md
@@ -0,0 +1,20 @@
+---
+"@cloudoperators/juno-ui-components": minor
+---
+
+Add h1–h6 base typography styles aligned with the Juno design system scale.
+
+- `global.css` and `theme.css`: h1–h6 defined in `@layer base` with IBM Plex Sans Bold, rem font sizes, and plain CSS `line-height` values. `theme.css` uses `var(--font-sans)` instead of a hardcoded font stack.
+- `FormattedText`: heading sizes and line-heights aligned with the scale; h5 corrected (1.03rem → 1.125rem); h6 added; redundant `font-weight`/`font-style` declarations removed.
+- `ContentHeading`: removed `jn:font-bold` and `jn:text-lg` overrides that were overriding the h1 base style with a smaller size.
+
+Fix h1 misuse in components that rendered UI labels as `
`, which caused unintended size changes after the global h1 style was introduced. Heading levels were chosen to reflect both visual size and accessibility (correct heading outline for screen readers).
+
+- `Modal`: title changed from `` to ``. The modal has `role="dialog"` which creates an isolated landmark; the title is referenced via `aria-labelledby`. A heading inside a dialog is semantically correct and independent of the page outline. `` matches the previous visual size (~20px).
+- `Form`: title changed from `` to ``. A form is a named content section; a heading is appropriate. `` matches the previous visual size (~24px).
+- `FormSection`: title changed from `` to ``. One level below `Form` (``) in the heading hierarchy. `` was chosen over `` (visual match) to maintain correct nesting.
+- `SignInForm`: title changed from `` to ``. The form is often the primary content of a page but may also be embedded; `` is a safe default for both cases.
+- `IntroBox`: title changed from `` to `
`. An info box is not a navigable section; adding it to the heading outline would confuse screen reader users navigating by heading. Bold styling preserved via existing `introboxHeading` class.
+- `global.css`: replaced `font-weight: 700` with `@apply jn:font-bold` in h1–h6 base styles for consistency with Tailwind conventions.
+- `theme.css`: same — replaced `font-weight: 700` with `@apply font-bold` (no `jn:` prefix, consistent with existing `@apply` usage in that file).
+- `Modal`: ReactNode titles now render as `
` instead of `
` to avoid invalid HTML (block elements inside heading elements). String titles remain ``. Both approaches are equivalent for assistive technologies.
diff --git a/packages/ui-components/src/components/ContentHeading/ContentHeading.component.tsx b/packages/ui-components/src/components/ContentHeading/ContentHeading.component.tsx
index 87aaf3a080..cf22f9c7d1 100644
--- a/packages/ui-components/src/components/ContentHeading/ContentHeading.component.tsx
+++ b/packages/ui-components/src/components/ContentHeading/ContentHeading.component.tsx
@@ -6,8 +6,6 @@
import React, { HTMLAttributes, ReactNode } from "react"
const baseHeadingStyles = `
- jn:font-bold
- jn:text-lg
jn:text-theme-high
jn:pb-2
`
diff --git a/packages/ui-components/src/components/Form/Form.component.tsx b/packages/ui-components/src/components/Form/Form.component.tsx
index e6b610b800..b252ede711 100644
--- a/packages/ui-components/src/components/Form/Form.component.tsx
+++ b/packages/ui-components/src/components/Form/Form.component.tsx
@@ -10,8 +10,6 @@ const formBaseStyles = `
`
const formTitleStyles = `
- jn:text-2xl
- jn:font-bold
jn:mb-4
`
@@ -45,7 +43,7 @@ export interface FormProps extends FormHTMLAttributes {
export const Form = ({ title = "", className = "", children, ...props }: FormProps): ReactNode => {
return (
)
diff --git a/packages/ui-components/src/components/FormSection/FormSection.component.tsx b/packages/ui-components/src/components/FormSection/FormSection.component.tsx
index 38f160985e..df39d46e60 100644
--- a/packages/ui-components/src/components/FormSection/FormSection.component.tsx
+++ b/packages/ui-components/src/components/FormSection/FormSection.component.tsx
@@ -11,8 +11,6 @@ const formSectionBaseStyles = `
`
const headingStyles = `
- jn:text-lg
- jn:font-bold
jn:mb-4
`
@@ -45,7 +43,7 @@ export interface FormSectionProps extends HTMLAttributes {
export const FormSection = ({ title = "", children, className = "", ...props }: FormSectionProps): ReactNode => {
return (
- {title ? {title}
: null}
+ {title ? {title}
: null}
{children}
)
diff --git a/packages/ui-components/src/components/FormattedText/formatted-text.css b/packages/ui-components/src/components/FormattedText/formatted-text.css
index 68d5bddfec..0ef051be29 100644
--- a/packages/ui-components/src/components/FormattedText/formatted-text.css
+++ b/packages/ui-components/src/components/FormattedText/formatted-text.css
@@ -36,42 +36,40 @@
h1 {
font-size: 1.69rem;
- font-weight: 700;
- line-height: 1.11; /* round(40 / 36) */
+ line-height: 1.375;
margin-top: 0;
margin-bottom: 2rem; /* rem(32) */
}
h2 {
font-size: 1.56rem;
- font-weight: 700;
- line-height: 160%;
+ line-height: 1.375;
margin-top: 3rem; /* rem(48) */
margin-bottom: 1.5rem; /* rem(24) */
}
h3 {
font-size: 1.44rem;
- font-weight: 700;
- line-height: 160%;
+ line-height: 1.375;
margin-top: 1rem;
margin-bottom: 0.75rem; /* rem(12) */
}
h4 {
font-size: 1.28rem;
- font-style: normal;
- font-weight: 700;
- line-height: 160%;
+ line-height: 1.625;
margin-top: 1.5rem; /* rem(24) */
margin-bottom: 0.5rem; /* rem(8) */
}
h5 {
- font-size: 1.03rem;
- font-style: normal;
- font-weight: 700;
- line-height: 160%;
+ font-size: 1.125rem;
+ line-height: 1.625;
+ }
+
+ h6 {
+ font-size: 1rem;
+ line-height: 1.5;
}
img,
diff --git a/packages/ui-components/src/components/IntroBox/IntroBox.component.tsx b/packages/ui-components/src/components/IntroBox/IntroBox.component.tsx
index 0a719dddab..776519aa57 100644
--- a/packages/ui-components/src/components/IntroBox/IntroBox.component.tsx
+++ b/packages/ui-components/src/components/IntroBox/IntroBox.component.tsx
@@ -119,7 +119,7 @@ export const IntroBox = ({
>
- {title ?
{title}
: ""}
+ {title ?
{title}
: ""}
{children ? children :
{text}
}
diff --git a/packages/ui-components/src/components/Message/Message.component.tsx b/packages/ui-components/src/components/Message/Message.component.tsx
index 47294c3dbc..990406fcca 100644
--- a/packages/ui-components/src/components/Message/Message.component.tsx
+++ b/packages/ui-components/src/components/Message/Message.component.tsx
@@ -264,7 +264,7 @@ export const Message = ({
- {title &&
{title}
}
+ {title &&
{title}}
{children || text}
{dismissible && (
diff --git a/packages/ui-components/src/components/Modal/Modal.component.tsx b/packages/ui-components/src/components/Modal/Modal.component.tsx
index 71839c3e5d..327487bbe1 100644
--- a/packages/ui-components/src/components/Modal/Modal.component.tsx
+++ b/packages/ui-components/src/components/Modal/Modal.component.tsx
@@ -57,7 +57,9 @@ const headerstyles = `
`
const titlestyles = `
- jn:text-xl
+ jn:font-sans
+ jn:text-[1.28rem]
+ jn:leading-relaxed
jn:font-bold
`
@@ -177,13 +179,13 @@ export const Modal = ({
}
if (typeof modalTitle === "string") {
return (
-
+
{modalTitle}
-
+
)
}
return (
-
+
{modalTitle}
)
diff --git a/packages/ui-components/src/components/Modal/Modal.test.tsx b/packages/ui-components/src/components/Modal/Modal.test.tsx
index 30034466f9..f3b9306703 100644
--- a/packages/ui-components/src/components/Modal/Modal.test.tsx
+++ b/packages/ui-components/src/components/Modal/Modal.test.tsx
@@ -53,7 +53,7 @@ describe("Modal", () => {
)
const dialog = screen.getByRole("dialog")
- const titleWrapper = screen.getByText("Node Title").closest("div")
+ const titleWrapper = screen.getByRole("heading", { level: 4 })
expect(dialog).toBeInTheDocument()
expect(titleWrapper).toBeInTheDocument()
diff --git a/packages/ui-components/src/components/PopupMenu/PopupMenu.component.tsx b/packages/ui-components/src/components/PopupMenu/PopupMenu.component.tsx
index ca657b0cfe..9c40e50799 100644
--- a/packages/ui-components/src/components/PopupMenu/PopupMenu.component.tsx
+++ b/packages/ui-components/src/components/PopupMenu/PopupMenu.component.tsx
@@ -398,7 +398,7 @@ export const PopupMenuSectionHeading = ({
}: PopupMenuSectionHeadingProps): ReactNode => {
return (
- {label && label.length ? label : children}
+ {label && label.length ? label : children}
)
}
diff --git a/packages/ui-components/src/components/SignInForm/SignInForm.component.tsx b/packages/ui-components/src/components/SignInForm/SignInForm.component.tsx
index 469ee20ce1..c813fae5d2 100644
--- a/packages/ui-components/src/components/SignInForm/SignInForm.component.tsx
+++ b/packages/ui-components/src/components/SignInForm/SignInForm.component.tsx
@@ -8,8 +8,6 @@ import { Message } from "../Message"
import { Stack } from "../Stack"
const signInFormTitleStyles = `
- jn:text-xl
- jn:font-bold
jn:text-theme-highest
jn:mb-4
`
@@ -75,7 +73,7 @@ export const SignInForm = ({
return (