Skip to content

Commit f3abcda

Browse files
eirikbackerBarsnesmimarz
authored
fix(List): css principles (#2348)
- Using principles from #2295 - Removing `asChild` on ListOrdered and ListUnordered as using other tags will break HTML validity and remove built in list-type styling - Removing the wrapping `div` caused by `List.Root` so root truly becomes a pure context provider and simplify DOM - Replacing `List.Root`, `List.Heading`, `List.Unordered`, `List.Ordered` with `<List variant="unordered | ordered | none">` for more HTML-aligned API and less verbose DOM - Automatic connect heading with list if provided - not a must, but a nice a11y enhancement - Fix a11y for VoiceOver when `list-style: none` --------- Co-authored-by: Tobias Barsnes <[email protected]> Co-authored-by: Michael Marszalek <[email protected]>
1 parent 5a46662 commit f3abcda

File tree

18 files changed

+456
-546
lines changed

18 files changed

+456
-546
lines changed

.changeset/happy-hounds-tie.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@digdir/designsystemet-css": patch
3+
"@digdir/designsystemet-react": patch
4+
---
5+
6+
List: Remove `List.Root` and `List.Heading`, which changes API

apps/storefront/app/god-praksis/tilgjengelighet/kontrast/page.mdx

+67-75
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import {
22
Card,
33
CardContent,
4+
Heading,
45
List,
5-
ListRoot,
66
ListHeading,
77
ListUnordered,
88
ListItem,
99
} from '@digdir/designsystemet-react';
1010

11-
{/* The importing should allow likeList.Root, but it throw a react error */}
12-
1311
import { Image } from '@components';
1412
import { PageLayout } from '@layouts';
1513
import { Contributors } from '@blog';
@@ -40,92 +38,86 @@ Alle brukerne, også de med svekket syn, skal kunne se innholdet i digitale tjen
4038

4139
<Card color='brand3'>
4240
<CardContent>
43-
<ListRoot>
44-
<ListHeading
45-
level={3}
46-
size='xs'
47-
>
48-
Gjeldende regelverk, WCAG 2.1
49-
</ListHeading>
50-
<ListUnordered>
51-
<ListItem>
52-
**1.4.3 Kontrast (minimum) (Nivå AA)**: Kontrastforholdet mellom
53-
teksten og bakgrunnen er minst 4,5:1. [1.4.3 Kontrast (minimum), WCAG
54-
2.1
55-
(w3.org)](https://www.w3.org/Translations/WCAG21-no/#contrast-minimum)
56-
</ListItem>
57-
<ListItem>
58-
**1.4.11 Kontrast for ikke-tekstlig innhold (Nivå AA)**: Den visuelle
59-
presentasjonen av det følgende har et kontrastforhold på minst 3:1 mot
60-
farge(r) som ligger ved siden av. [1.4.11 Kontrast for ikke-tekstlig
61-
innhold, WCAG 2.1
62-
(w3.org)](https://www.w3.org/Translations/WCAG21-no/#non-text-contrast)
63-
</ListItem>
64-
</ListUnordered>
65-
</ListRoot>
41+
<Heading
42+
level={3}
43+
size='xs'
44+
>
45+
Gjeldende regelverk, WCAG 2.1
46+
</Heading>
47+
<ListUnordered>
48+
<ListItem>
49+
**1.4.3 Kontrast (minimum) (Nivå AA)**: Kontrastforholdet mellom
50+
teksten og bakgrunnen er minst 4,5:1. [1.4.3 Kontrast (minimum), WCAG
51+
2.1
52+
(w3.org)](https://www.w3.org/Translations/WCAG21-no/#contrast-minimum)
53+
</ListItem>
54+
<ListItem>
55+
**1.4.11 Kontrast for ikke-tekstlig innhold (Nivå AA)**: Den visuelle
56+
presentasjonen av det følgende har et kontrastforhold på minst 3:1 mot
57+
farge(r) som ligger ved siden av. [1.4.11 Kontrast for ikke-tekstlig
58+
innhold, WCAG 2.1
59+
(w3.org)](https://www.w3.org/Translations/WCAG21-no/#non-text-contrast)
60+
</ListItem>
61+
</ListUnordered>
6662
</CardContent>
6763
</Card>
6864

6965
<br />
7066

7167
<Card color='brand2'>
7268
<CardContent>
73-
<ListRoot>
74-
<ListHeading
75-
level={3}
76-
size='xs'
77-
>
78-
Fremtidig eller strengere:
79-
</ListHeading>
80-
<ListUnordered>
81-
<ListItem>
82-
**1.4.6 Kontrast** (forbedret) (Nivå AAA): Den visuelle presentasjonen
83-
av tekst og bilder av tekst har et kontrastforhold på minst 7:1,
84-
unntatt uvesentlig tekst og skriftstørrelser større enn 18px eller
85-
14px fet. [ 1.4.6 Kontrast (forbedret), WCAG 2.1
86-
(w3.org)](https://www.w3.org/Translations/WCAG21-no/#contrast-enhanced)
87-
</ListItem>
88-
<ListItem>
89-
**WCAG 2.2: 2.4.13** Focus Appearance (Nivå AAA), om utseende til
90-
fokusmarkering krever at fokusindikator har en kontrastverdi på 3:1
91-
mellom samme piksler i fokusert og ikke-fokusert tilstand.
92-
[Understanding Success Criterion 2.4.13: Focus Appearance | WAI |
93-
W3C](https://www.w3.org/WAI/WCAG22/Understanding/focus-appearance.html)
94-
</ListItem>
95-
<ListItem>
96-
**WCAG 3** har et krav om farge og kontrast, visuell kontrast i tekst
97-
(sølv): Sørg for tilstrekkelig kontrast mellom tekst i forgrunnen og
98-
bakgrunnen for teksten. Her brukes det en ny metode, med navn APCA,
99-
for å regne ut kontrasten.
100-
</ListItem>
101-
</ListUnordered>
102-
</ListRoot>
69+
<Heading
70+
level={3}
71+
size='xs'
72+
>
73+
Fremtidig eller strengere:
74+
</Heading>
75+
<ListUnordered>
76+
<ListItem>
77+
**1.4.6 Kontrast** (forbedret) (Nivå AAA): Den visuelle presentasjonen
78+
av tekst og bilder av tekst har et kontrastforhold på minst 7:1,
79+
unntatt uvesentlig tekst og skriftstørrelser større enn 18px eller
80+
14px fet. [ 1.4.6 Kontrast (forbedret), WCAG 2.1
81+
(w3.org)](https://www.w3.org/Translations/WCAG21-no/#contrast-enhanced)
82+
</ListItem>
83+
<ListItem>
84+
**WCAG 2.2: 2.4.13** Focus Appearance (Nivå AAA), om utseende til
85+
fokusmarkering krever at fokusindikator har en kontrastverdi på 3:1
86+
mellom samme piksler i fokusert og ikke-fokusert tilstand.
87+
[Understanding Success Criterion 2.4.13: Focus Appearance | WAI |
88+
W3C](https://www.w3.org/WAI/WCAG22/Understanding/focus-appearance.html)
89+
</ListItem>
90+
<ListItem>
91+
**WCAG 3** har et krav om farge og kontrast, visuell kontrast i tekst
92+
(sølv): Sørg for tilstrekkelig kontrast mellom tekst i forgrunnen og
93+
bakgrunnen for teksten. Her brukes det en ny metode, med navn APCA,
94+
for å regne ut kontrasten.
95+
</ListItem>
96+
</ListUnordered>
10397
</CardContent>
10498
</Card>
10599

106100
## Mer presis metode
107101

108102
WCAG 3.0 foreslår nå en mer presis metode enn dagens standard, for å kalkulere kontrast og sette terskelverdier.
109103

110-
<ListRoot>
111-
<ListUnordered>
112-
<ListItem>
113-
Metoden forbedrer hvordan verdien mellom to farger bestemmes, og skiller
114-
også på om fargene er i forgrunnen eller i bakgrunnen.
115-
</ListItem>
116-
<ListItem>
117-
Den setter også tydelige terskelverdier eller målverdier for valg av font,
118-
tekststørrelse og font-vekt. Metoden heter Advanced Perceptual Contrast
119-
Algorithm (APCA).
120-
</ListItem>
121-
<ListItem>
122-
Målet vårt er å ligge over AAA-krav i WCAG 2.1, og vi vil dermed ligge
123-
nærmere terskelverdiene i APCA. Det øker sjansen for at vi klarer å
124-
oppfylle kravet om at alle, også svaksynte, skal kunne se innholdet på
125-
nettstedet.
126-
</ListItem>
127-
</ListUnordered>
128-
</ListRoot>
104+
<ListUnordered>
105+
<ListItem>
106+
Metoden forbedrer hvordan verdien mellom to farger bestemmes, og skiller
107+
også på om fargene er i forgrunnen eller i bakgrunnen.
108+
</ListItem>
109+
<ListItem>
110+
Den setter også tydelige terskelverdier eller målverdier for valg av font,
111+
tekststørrelse og font-vekt. Metoden heter Advanced Perceptual Contrast
112+
Algorithm (APCA).
113+
</ListItem>
114+
<ListItem>
115+
Målet vårt er å ligge over AAA-krav i WCAG 2.1, og vi vil dermed ligge
116+
nærmere terskelverdiene i APCA. Det øker sjansen for at vi klarer å
117+
oppfylle kravet om at alle, også svaksynte, skal kunne se innholdet på
118+
nettstedet.
119+
</ListItem>
120+
</ListUnordered>
129121

130122
## I dag bruker vi en høyere standard enn kravene til tilgjengelighet
131123

apps/storefront/mdx-components.tsx

+4-17
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
Link,
1212
ListItem,
1313
ListOrdered,
14-
ListRoot,
1514
ListUnordered,
1615
Paragraph,
1716
Table,
@@ -26,22 +25,10 @@ import type { MDXComponents } from 'mdx/types';
2625
export function useMDXComponents(components: MDXComponents): MDXComponents {
2726
return {
2827
...components,
29-
p: (props: ParagraphProps) => {
30-
return <Paragraph {...props} spacing />;
31-
},
32-
a: (props) => {
33-
return <Link {...(props as LinkProps)} />;
34-
},
35-
ol: (props: ListOrderedProps) => (
36-
<ListRoot>
37-
<ListOrdered {...props} />
38-
</ListRoot>
39-
),
40-
ul: (props: ListUnorderedProps) => (
41-
<ListRoot>
42-
<ListUnordered {...props} />
43-
</ListRoot>
44-
),
28+
p: (props: ParagraphProps) => <Paragraph {...props} spacing />,
29+
a: (props) => <Link {...(props as LinkProps)} />,
30+
ol: (props: ListOrderedProps) => <ListOrdered {...props} />,
31+
ul: (props: ListUnorderedProps) => <ListUnordered {...props} />,
4532
li: (props: ListItemProps) => <ListItem {...props}></ListItem>,
4633
h1: (props: HeadingProps) => (
4734
<Heading {...props} level={1} size='xl' spacing />

apps/storybook/.storybook/preview.tsx

+14-18
Original file line numberDiff line numberDiff line change
@@ -49,32 +49,28 @@ const components = {
4949
/>
5050
),
5151
ol: (props: Props) => (
52-
<List.Root>
53-
<List.Ordered
54-
{...props}
55-
style={{ maxWidth: '70ch' }}
56-
className='sb-unstyled'
57-
data-ds-color-mode='light'
58-
/>
59-
</List.Root>
52+
<List.Ordered
53+
{...props}
54+
style={{ maxWidth: '70ch' }}
55+
className='sb-unstyled'
56+
data-ds-color-mode='light'
57+
/>
6058
),
6159
ul: (props: Props) => (
62-
<List.Root>
63-
<List.Unordered
64-
{...props}
65-
style={{ maxWidth: '70ch' }}
66-
className='sb-unstyled'
67-
data-ds-color-mode='light'
68-
/>
69-
</List.Root>
60+
<List.Unordered
61+
{...props}
62+
style={{ maxWidth: '70ch' }}
63+
className='sb-unstyled'
64+
data-ds-color-mode='light'
65+
/>
7066
),
7167
li: (props: Props) => (
7268
<List.Item
7369
{...props}
7470
className='sb-unstyled'
7571
style={{ maxWidth: '70ch' }}
7672
data-ds-color-mode='light'
77-
></List.Item>
73+
/>
7874
),
7975
a: (props: LinkProps) => {
8076
// if link starts with /, add current path to link
@@ -86,7 +82,7 @@ const components = {
8682
href={href}
8783
className='sb-unstyled'
8884
data-ds-color-mode='light'
89-
></Link>
85+
/>
9086
);
9187
},
9288
table: (props: Props) => (

packages/css/list.css

+23-13
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
.ds-list {
2+
--dsc-list-font-size: var(--ds-font-size-5);
23
--dsc-list-padding-left: var(--ds-spacing-6);
4+
--dsc-list-spacing: var(--ds-spacing-3);
5+
--dsc-list-spacing-nested: var(--ds-spacing-2);
36

7+
font-size: var(--dsc-list-font-size); /* Replace with composes paragraph-md */
8+
line-height: var(--ds-line-height-md); /* Replace with composes paragraph-md */
9+
margin: 0;
410
padding-left: var(--dsc-list-padding-left);
5-
}
611

7-
.ds-list--sm {
8-
--dsc-list-padding-left: var(--ds-spacing-4);
9-
}
12+
& > li + li { margin-top: var(--dsc-list-spacing) }
13+
& > li > :is(ol, ul) { --dsc-list-spacing: var(--dsc-list-spacing-nested) } /* Shrink spacing a bit when nested */
1014

11-
.ds-list--md,
12-
.ds-list--lg {
13-
--dsc-list-padding-left: var(--ds-spacing-6);
14-
}
15+
/* Add zero-width space to fix VoiceOver: https://gerardkcohen.me/writing/2017/voiceover-list-style-type.html
16+
* This can also be acheived by using role="list" + role="listitem", but is nice to solve with CSS avoiding cluttered HTML
17+
*/
18+
& > li::before {
19+
content: "\200B";
20+
position: absolute;
21+
}
1522

16-
.ds-list__item {
17-
margin-bottom: var(--ds-spacing-2);
18-
}
23+
&[data-size="sm"] {
24+
--dsc-list-padding-left: var(--ds-spacing-4);
25+
--dsc-list-font-size: var(--ds-font-size-4); /* Replace with composes paragraph-sm */
26+
}
1927

20-
.ds-list__item > .ds-list {
21-
margin-top: var(--ds-spacing-2);
28+
&[data-size="lg"] {
29+
--dsc-list-font-size: var(--ds-font-size-6); /* Replace with composes paragraph-sm */
30+
--dsc-list-spacing: var(--ds-spacing-4);
31+
}
2232
}
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,45 @@
1-
import { useContext, useEffect } from 'react';
1+
import { forwardRef, useContext, useEffect } from 'react';
22

3-
import type { ListHeadingProps } from '../List';
4-
import { List } from '../List';
3+
import { Heading, type HeadingProps } from '../Typography/Heading';
54

6-
import { ErrorSummaryContext } from './ErrorSummaryRoot';
5+
import {
6+
ErrorSummaryContext,
7+
type ErrorSummaryProps,
8+
} from './ErrorSummaryRoot';
79

8-
export type ErrorSummaryHeadingProps = ListHeadingProps;
10+
export type ErrorSummaryHeadingProps = HeadingProps;
911

10-
export const ErrorSummaryHeading = ({
11-
id,
12-
...rest
13-
}: ErrorSummaryHeadingProps) => {
14-
const { headingId, setHeadingId } = useContext(ErrorSummaryContext);
12+
const HEADING_SIZE_MAP: {
13+
[key in NonNullable<ErrorSummaryProps['size']>]: HeadingProps['size'];
14+
} = {
15+
sm: '2xs',
16+
md: 'xs',
17+
lg: 'sm',
18+
} as const;
19+
20+
export const ErrorSummaryHeading = forwardRef<
21+
HTMLHeadingElement,
22+
ErrorSummaryHeadingProps
23+
>(function ErrorSummaryHeading(
24+
{ className, id, ...rest }: ErrorSummaryHeadingProps,
25+
ref,
26+
) {
27+
const { size, headingId, setHeadingId } = useContext(ErrorSummaryContext);
1528

1629
useEffect(() => {
17-
if (id && headingId !== id) {
18-
setHeadingId(id);
19-
}
30+
if (id && headingId !== id) setHeadingId(id);
2031
}, [headingId, id, setHeadingId]);
2132

2233
return (
23-
<List.Heading
24-
{...rest}
25-
id={headingId}
34+
<Heading
2635
className='ds-error-summary__heading'
36+
id={headingId}
37+
size={HEADING_SIZE_MAP[size ?? 'md']}
38+
spacing
39+
ref={ref}
40+
{...rest}
2741
/>
2842
);
29-
};
43+
});
3044

3145
ErrorSummaryHeading.displayName = 'ErrorSummaryHeading';

0 commit comments

Comments
 (0)