@hubspot/cms-component-library 0.3.9 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,10 +29,10 @@
29
29
  }
30
30
 
31
31
  &:focus-visible {
32
- background-color: var(--hs-button-backgroundColor-focus, var(--hs-button-backgroundColor));
33
- color: var(--hs-button-color-focus, var(--hs-button-color));
34
- border: var(--hs-button-border-focus, var(--hs-button-border));
35
- outline: 2px solid;
32
+ background-color: var(--hs-button-backgroundColor-hover, var(--hs-button-backgroundColor));
33
+ color: var(--hs-button-color-hover, var(--hs-button-color));
34
+ border: var(--hs-button-border-hover, var(--hs-button-border));
35
+ outline: 2px solid Highlight;
36
36
  outline-offset: 2px;
37
37
  }
38
38
 
@@ -22,7 +22,7 @@ const ButtonComponent = ({
22
22
  const sharedProps = {
23
23
  className: defaultClasses,
24
24
  style: style,
25
- 'data-buttons-variant': variant,
25
+ 'data-button-variant': variant,
26
26
  };
27
27
 
28
28
  const RenderWithIcon = ({
@@ -4,7 +4,7 @@ import { StyleFieldsProps } from './types.js';
4
4
  const StyleFields = ({
5
5
  cardVariantLabel = 'Card variant',
6
6
  cardVariantName = 'cardVariant',
7
- cardVariantDefault = { variantName: 'cardStyle1' },
7
+ cardVariantDefault = { variant_name: 'card1' },
8
8
  }: StyleFieldsProps) => {
9
9
  return (
10
10
  <VariantSelectionField
@@ -4,7 +4,7 @@ import { CardProps } from './types.js';
4
4
  import StyleFields from './StyleFields.js';
5
5
 
6
6
  const CardComponent = ({
7
- variant = 'cardStyle1',
7
+ variant = 'card1',
8
8
  as: Component = 'div',
9
9
  className = '',
10
10
  style = {},
@@ -34,7 +34,7 @@ Card/
34
34
  **Props:**
35
35
  ```tsx
36
36
  {
37
- variant?: 'cardStyle1' | 'cardStyle2' | 'cardStyle3' | 'cardStyle4'; // Theme variant (default: 'cardStyle1')
37
+ variant?: 'card1' | 'card2' | 'card3' | 'card4'; // Theme variant (default: 'card1')
38
38
  as?: 'div' | 'article' | 'section'; // HTML element to render (default: 'div')
39
39
  children?: React.ReactNode; // Card content
40
40
  className?: string; // Additional CSS classes
@@ -58,7 +58,7 @@ import Card from '@hubspot/cms-component-library/Card';
58
58
  ### Semantic Article Card
59
59
 
60
60
  ```tsx
61
- <Card as="article" variant="cardStyle3">
61
+ <Card as="article" variant="card3">
62
62
  <h3>Blog Post Title</h3>
63
63
  <p>Article content goes here...</p>
64
64
  </Card>
@@ -97,7 +97,7 @@ Configurable props for customizing field labels, names, and defaults:
97
97
  <Card.StyleFields
98
98
  cardVariantLabel="Card variant"
99
99
  cardVariantName="cardVariant"
100
- cardVariantDefault={{ variant_name: 'cardStyle1' }}
100
+ cardVariantDefault={{ variant_name: 'card1' }}
101
101
  />
102
102
  ```
103
103
 
@@ -43,7 +43,7 @@ type Story = StoryObj<typeof meta>;
43
43
 
44
44
  export const Default: Story = {
45
45
  args: {
46
- variant: 'cardStyle1',
46
+ variant: 'card1',
47
47
  },
48
48
  render: args => (
49
49
  <Card {...args}>
@@ -60,7 +60,7 @@ export const Variants: Story = {
60
60
  <SBContainer flex direction="column" gap="large">
61
61
  <SBContainer addBackground>
62
62
  <h4>Card Style 1 (Light)</h4>
63
- <Card variant="cardStyle1">
63
+ <Card variant="card1">
64
64
  <CardHeading>Card Style 1</CardHeading>
65
65
  <CardDivider />
66
66
  <CardContent>Light background with dark text.</CardContent>
@@ -69,7 +69,7 @@ export const Variants: Story = {
69
69
 
70
70
  <SBContainer addBackground>
71
71
  <h4>Card Style 2 (Subtle)</h4>
72
- <Card variant="cardStyle2">
72
+ <Card variant="card2">
73
73
  <CardHeading>Card Style 2</CardHeading>
74
74
  <CardDivider />
75
75
  <CardContent>Subtle background with dark text.</CardContent>
@@ -78,7 +78,7 @@ export const Variants: Story = {
78
78
 
79
79
  <SBContainer addBackground>
80
80
  <h4>Card Style 3 (Dark)</h4>
81
- <Card variant="cardStyle3">
81
+ <Card variant="card3">
82
82
  <CardHeading>Card Style 3</CardHeading>
83
83
  <CardDivider />
84
84
  <CardContent>Dark background with light text.</CardContent>
@@ -87,7 +87,7 @@ export const Variants: Story = {
87
87
 
88
88
  <SBContainer addBackground>
89
89
  <h4>Card Style 4 (Darker)</h4>
90
- <Card variant="cardStyle4">
90
+ <Card variant="card4">
91
91
  <CardHeading>Card Style 4</CardHeading>
92
92
  <CardDivider />
93
93
  <CardContent>Darker background with light text.</CardContent>
@@ -1,27 +1,27 @@
1
1
  .cardContainer {
2
2
  /* card Variant Styles */
3
- :global([data-card-variant='cardStyle1']) {
3
+ :global([data-card-variant='card1']) {
4
4
  --hs-card-borderRadius: 10px;
5
5
  --hs-card-backgroundColor: rgb(255, 255, 255);
6
6
  --hs-card-color: rgb(28, 28, 31);
7
7
  --hs-card-border: 1px solid #e0e0e0;
8
8
  }
9
9
 
10
- :global([data-card-variant='cardStyle2']) {
10
+ :global([data-card-variant='card2']) {
11
11
  --hs-card-borderRadius: 10px;
12
12
  --hs-card-backgroundColor: rgb(249, 249, 249);
13
13
  --hs-card-color: rgb(28, 28, 31);
14
14
  --hs-card-border: 1px solid #e0e0e0;
15
15
  }
16
16
 
17
- :global([data-card-variant='cardStyle3']) {
17
+ :global([data-card-variant='card3']) {
18
18
  --hs-card-borderRadius: 10px;
19
19
  --hs-card-backgroundColor: rgb(28, 28, 31);
20
20
  --hs-card-color: rgb(255,255,255);
21
21
  --hs-card-border: 1px solid #e0e0e0;
22
22
  }
23
23
 
24
- :global([data-card-variant='cardStyle4']) {
24
+ :global([data-card-variant='card4']) {
25
25
  --hs-card-borderRadius: 10px;
26
26
  --hs-card-backgroundColor: rgb(36, 36, 36);
27
27
  --hs-card-color: rgb(255,255,255);
@@ -1,8 +1,8 @@
1
1
  export type CardVariant =
2
- | 'cardStyle1'
3
- | 'cardStyle2'
4
- | 'cardStyle3'
5
- | 'cardStyle4';
2
+ | 'card1'
3
+ | 'card2'
4
+ | 'card3'
5
+ | 'card4';
6
6
 
7
7
  export type CardAs = 'div' | 'article' | 'section';
8
8
 
@@ -4,7 +4,7 @@ import { StyleFieldsProps } from './types.js';
4
4
  const StyleFields = ({
5
5
  formVariantLabel = 'Form variant',
6
6
  formVariantName = 'formVariant',
7
- formVariantDefault = { variant_name: 'primaryForm' },
7
+ formVariantDefault = { variant_name: 'form1' },
8
8
  }: StyleFieldsProps) => {
9
9
  return (
10
10
  <VariantSelectionField
@@ -123,7 +123,7 @@ Configurable props for variant selection:
123
123
  <Form.StyleFields
124
124
  formVariantLabel="Form variant"
125
125
  formVariantName="formVariant"
126
- formVariantDefault={{ variant_name: 'primaryForm' }}
126
+ formVariantDefault={{ variant_name: 'form1' }}
127
127
  />
128
128
  ```
129
129
 
@@ -135,7 +135,7 @@ Configurable props for variant selection:
135
135
  {
136
136
  formVariantLabel?: string; // Label shown in the editor (default: "Form variant")
137
137
  formVariantName?: string; // Field name (default: "formVariant")
138
- formVariantDefault?: { variant_name: string }; // Default variant (default: { variant_name: 'primaryForm' })
138
+ formVariantDefault?: { variant_name: string }; // Default variant (default: { variant_name: 'form1' })
139
139
  }
140
140
  ```
141
141
 
@@ -1,25 +1,18 @@
1
- import { ChoiceField } from '@hubspot/cms-components/fields';
1
+ import { VariantSelectionField } from '@hubspot/cms-components/fields';
2
2
  import { StyleFieldsProps } from './types.js';
3
3
 
4
- // !todo: may not need later, but keeping for now in case we have variant system for links
5
4
  const StyleFields = ({
6
- linkVariantLabel = 'Link variant',
7
- linkVariantName = 'linkVariant',
8
- linkVariantDefault = 'primary',
5
+ variantLabel = 'Link variant',
6
+ variantName = 'linkVariant',
7
+ variantDefault = { variant_name: 'primaryLink' },
9
8
  }: StyleFieldsProps) => {
10
9
  return (
11
- <>
12
- <ChoiceField
13
- label={linkVariantLabel}
14
- name={linkVariantName}
15
- choices={[
16
- ['primary', 'Primary'],
17
- ['secondary', 'Secondary'],
18
- ['tertiary', 'Tertiary'],
19
- ]}
20
- default={linkVariantDefault}
21
- />
22
- </>
10
+ <VariantSelectionField
11
+ label={variantLabel}
12
+ name={variantName}
13
+ variantDefinitionName="link"
14
+ default={variantDefault}
15
+ />
23
16
  );
24
17
  };
25
18
 
@@ -1,21 +1,23 @@
1
1
  .link {
2
- display: var(--hscl-link-display, inline-block);
3
- color: var(--hscl-link-color);
4
- text-decoration: var(--hscl-link-textDecoration, underline);
2
+ display: inline-block;
3
+ color: var(--hs-link-color);
4
+ text-decoration: var(--hs-link-textDecoration);
5
5
  cursor: pointer;
6
6
 
7
7
  &:hover {
8
- color: var(--hscl-link-color-hover);
9
- text-decoration: var(--hscl-link-textDecoration-hover, underline);
8
+ color: var(--hs-link-color-hover);
9
+ text-decoration: var(--hs-link-textDecoration-hover);
10
10
  }
11
11
 
12
12
  &:focus-visible {
13
- outline: var(--hscl-link-outlineWidth-focus, 2px) solid var(--hscl-link-outlineColor-focus, currentColor);
14
- outline-offset: var(--hscl-link-outlineOffset-focus, 2px);
13
+ color: var(--hs-link-color-hover);
14
+ text-decoration: var(--hs-link-textDecoration-hover);
15
+ outline: 2px solid Highlight;
16
+ outline-offset: 2px;
15
17
  }
16
18
 
17
19
  &:active {
18
- color: var(--hscl-link-color-active, var(--hscl-link-color));
19
- text-decoration: var(--hscl-link-textDecoration-active, var(--hscl-link-textDecoration, underline));
20
+ color: var(--hs-link-color-active, var(--hs-link-color));
21
+ text-decoration: var(--hs-link-textDecoration-active, var(--hs-link-textDecoration));
20
22
  }
21
23
  }
@@ -2,7 +2,6 @@ import styles from './index.module.scss';
2
2
  import ContentFields from './ContentFields.js';
3
3
  import StyleFields from './StyleFields.js';
4
4
  import cx from '../utils/classname.js';
5
- import type { CSSVariables } from '../utils/types.js';
6
5
  import { LinkProps, BaseLinkProps, LinkHTMLProps } from './types.js';
7
6
  import {
8
7
  getLinkRel,
@@ -32,7 +31,7 @@ const getLinkValues = (props: Omit<LinkProps, keyof BaseLinkProps>) => {
32
31
  };
33
32
 
34
33
  const LinkComponent = ({
35
- variant = 'primary',
34
+ variant = 'primaryLink',
36
35
  className = '',
37
36
  style = {},
38
37
  children,
@@ -43,18 +42,14 @@ const LinkComponent = ({
43
42
  const defaultClasses = styles.link;
44
43
  const combinedClasses = cx(defaultClasses, className);
45
44
 
46
- const cssVariables: CSSVariables = {
47
- '--hscl-link-color': `var(--hscl-link-color-${variant})`,
48
- '--hscl-link-color-hover': `var(--hscl-link-color-hover-${variant})`,
49
- };
50
-
51
45
  return (
52
46
  <a
53
47
  className={combinedClasses}
54
- style={{ ...cssVariables, ...style }}
48
+ style={style}
55
49
  href={href}
56
50
  target={target}
57
51
  rel={rel}
52
+ data-link-variant={variant}
58
53
  {...anchorProps}
59
54
  >
60
55
  {children}
@@ -58,9 +58,9 @@ Pass a `linkField` prop containing HubSpot link field data. The component automa
58
58
  open_in_new_tab?: boolean; // Open in new tab
59
59
  no_follow?: boolean; // Add nofollow rel
60
60
  };
61
- variant?: 'primary' | 'secondary' | 'tertiary'; // Visual style variant (default: 'primary')
61
+ variant?: 'primaryLink' | 'secondaryLink'; // Visual style variant (default: 'primaryLink')
62
62
  className?: string; // Additional CSS classes
63
- style?: React.CSSProperties; // Inline styles (including CSS variables)
63
+ style?: React.CSSProperties; // Inline styles
64
64
  children?: React.ReactNode; // Link text/content
65
65
  }
66
66
  ```
@@ -74,9 +74,9 @@ Pass individual `href`, `target`, and `rel` props directly.
74
74
  href: string; // Link destination URL (required)
75
75
  target?: '_self' | '_blank' | '_parent' | '_top'; // Link target behavior
76
76
  rel?: string; // Link relationship (e.g., 'noopener noreferrer')
77
- variant?: 'primary' | 'secondary' | 'tertiary'; // Visual style variant (default: 'primary')
77
+ variant?: 'primaryLink' | 'secondaryLink'; // Visual style variant (default: 'primaryLink')
78
78
  className?: string; // Additional CSS classes
79
- style?: React.CSSProperties; // Inline styles (including CSS variables)
79
+ style?: React.CSSProperties; // Inline styles
80
80
  children?: React.ReactNode; // Link text/content
81
81
  }
82
82
  ```
@@ -107,21 +107,7 @@ export default function MyModule({ fieldValues }) {
107
107
  ```tsx
108
108
  <Link
109
109
  linkField={fieldValues.link}
110
- variant="secondary"
111
- >
112
- Visit HubSpot
113
- </Link>
114
- ```
115
-
116
- #### Field-based Link with Style Override
117
-
118
- ```tsx
119
- <Link
120
- linkField={fieldValues.link}
121
- style={{
122
- '--hscl-link-textDecoration': 'none',
123
- '--hscl-link-textDecoration-hover': 'underline',
124
- }}
110
+ variant="secondaryLink"
125
111
  >
126
112
  Visit HubSpot
127
113
  </Link>
@@ -163,17 +149,13 @@ import Link from '@hubspot/cms-component-library/Link';
163
149
  ### Using Variants
164
150
 
165
151
  ```tsx
166
- <Link href="https://www.hubspot.com" variant="primary">
152
+ <Link href="https://www.hubspot.com" variant="primaryLink">
167
153
  Primary Link
168
154
  </Link>
169
155
 
170
- <Link href="https://www.hubspot.com" variant="secondary">
156
+ <Link href="https://www.hubspot.com" variant="secondaryLink">
171
157
  Secondary Link
172
158
  </Link>
173
-
174
- <Link href="https://www.hubspot.com" variant="tertiary">
175
- Tertiary Link
176
- </Link>
177
159
  ```
178
160
 
179
161
  ## HubSpot CMS Integration
@@ -220,12 +202,12 @@ The Link component provides field definitions for easy integration with HubSpot
220
202
  <Link.StyleFields
221
203
  variantLabel="Link style"
222
204
  variantName="linkVariant"
223
- variantDefault="primary"
205
+ variantDefault={{ variant_name: 'primaryLink' }}
224
206
  />
225
207
  ```
226
208
 
227
209
  **Fields:**
228
- - `linkVariant`: Choice field for variant selection (primary, secondary, tertiary)
210
+ - `linkVariant`: VariantSelectionField for theme variant selection (primaryLink, secondaryLink)
229
211
 
230
212
  ### Module Usage Example
231
213
 
@@ -238,7 +220,7 @@ export default function NavigationModule({ fieldValues }) {
238
220
  return (
239
221
  <Link
240
222
  linkField={fieldValues.link}
241
- variant={fieldValues.linkVariant}
223
+ variant={fieldValues.style.linkVariant?.variantName}
242
224
  >
243
225
  {fieldValues.linkText}
244
226
  </Link>
@@ -255,7 +237,7 @@ export default function NavigationModule({ fieldValues }) {
255
237
  href="https://www.hubspot.com"
256
238
  target="_blank"
257
239
  rel="nofollow"
258
- variant="primary"
240
+ variant="primaryLink"
259
241
  >
260
242
  Go to HubSpot
261
243
  </Link>
@@ -269,69 +251,32 @@ Note: The field-based approach is simpler and less error-prone.
269
251
 
270
252
  ### Variants
271
253
 
272
- The Link component supports three visual style variants through the `variant` prop:
254
+ The Link component supports two visual style variants through the `variant` prop:
273
255
 
274
- - **primary** (default): Default link styling using `--hscl-link-color-primary` and `--hscl-link-color-hover-primary`
275
- - **secondary**: Alternative styling using `--hscl-link-color-secondary` and `--hscl-link-color-hover-secondary`
276
- - **tertiary**: Third alternative using `--hscl-link-color-tertiary` and `--hscl-link-color-hover-tertiary`
256
+ - **primaryLink** (default): Default link styling
257
+ - **secondaryLink**: Alternative link styling
277
258
 
278
- The component internally sets:
279
- ```tsx
280
- style={{
281
- '--hscl-link-color': `var(--hscl-link-color-${variant})`,
282
- '--hscl-link-color-hover': `var(--hscl-link-color-hover-${variant})`,
283
- }}
284
- ```
259
+ The component renders a `data-link-variant` attribute on the `<a>` element. The theme provides CSS variables scoped to `[data-link-variant]` selectors.
285
260
 
286
- ### CSS Variables
261
+ ### CSS Variables (Theme-provided)
287
262
 
288
- The Link component uses CSS variables for theming and customization:
263
+ The theme provides these CSS variables via `[data-link-variant]` selectors:
289
264
 
290
265
  **Base Styles:**
291
- - `--hscl-link-display`: Display property (default: inline-block)
292
- - `--hscl-link-color`: Text color (set by variant, no fallback)
293
- - `--hscl-link-textDecoration`: Text decoration (default: underline)
266
+ - `--hs-link-color`: Text color
267
+ - `--hs-link-textDecoration`: Text decoration
294
268
 
295
269
  **Hover States:**
296
- - `--hscl-link-color-hover`: Hover text color (set by variant, no fallback)
297
- - `--hscl-link-textDecoration-hover`: Hover text decoration (default: underline)
298
-
299
- **Variant-specific Variables (must be defined globally):**
300
- - `--hscl-link-color-primary`: Primary variant text color
301
- - `--hscl-link-color-hover-primary`: Primary variant hover text color
302
- - `--hscl-link-color-secondary`: Secondary variant text color
303
- - `--hscl-link-color-hover-secondary`: Secondary variant hover text color
304
- - `--hscl-link-color-tertiary`: Tertiary variant text color
305
- - `--hscl-link-color-hover-tertiary`: Tertiary variant hover text color
306
-
307
- ### Custom Styling Examples
270
+ - `--hs-link-color-hover`: Hover text color
271
+ - `--hs-link-textDecoration-hover`: Hover text decoration
308
272
 
309
- #### Override Text Decoration
273
+ **Active States:**
274
+ - `--hs-link-color-active`: Active text color (falls back to `--hs-link-color`)
275
+ - `--hs-link-textDecoration-active`: Active text decoration (falls back to `--hs-link-textDecoration`)
310
276
 
311
- ```tsx
312
- <Link
313
- href="https://www.hubspot.com"
314
- style={{
315
- '--hscl-link-textDecoration': 'none',
316
- '--hscl-link-textDecoration-hover': 'underline',
317
- }}
318
- >
319
- Hover for underline
320
- </Link>
321
- ```
322
-
323
- #### Override Display
324
-
325
- ```tsx
326
- <Link
327
- href="https://www.hubspot.com"
328
- style={{
329
- '--hscl-link-display': 'block',
330
- }}
331
- >
332
- Block-level Link
333
- </Link>
334
- ```
277
+ **Hardcoded Styles (not theme-configurable):**
278
+ - `display: inline-block`
279
+ - Focus outline: `2px solid currentColor` with `2px` offset
335
280
 
336
281
  ## Accessibility
337
282
 
@@ -349,9 +294,8 @@ The Link component follows accessibility best practices:
349
294
 
350
295
  - **Use linkField prop when available**: If you have HubSpot field data, use the `linkField` prop instead of manually extracting href/target/rel
351
296
  - **Do NOT mix approaches**: TypeScript will prevent you from using both `linkField` and `href` props together
352
- - **Use variants for consistency**: Choose the appropriate variant (primary/secondary/tertiary) rather than overriding colors directly
297
+ - **Use variants for consistency**: Choose the appropriate variant (primaryLink/secondaryLink) rather than overriding colors directly
353
298
  - **Security**: The component automatically adds `rel="noopener noreferrer"` when target is `_blank`
354
- - **CSS Variables**: Override design tokens using CSS variables for advanced customization
355
299
  - **Minimal styling**: Keep link styling simple; use Button component for prominent CTAs
356
300
 
357
301
  ## Common Patterns
@@ -359,7 +303,7 @@ The Link component follows accessibility best practices:
359
303
  ### Navigation Menu Item
360
304
 
361
305
  ```tsx
362
- <Link href="/dashboard" variant="secondary">
306
+ <Link href="/dashboard" variant="secondaryLink">
363
307
  Dashboard
364
308
  </Link>
365
309
  ```
@@ -4,12 +4,6 @@ import { LinkProps } from '../types.js';
4
4
  import { withLinkStyles } from './LinkDecorator.js';
5
5
  import { SBContainer, SBFocusWrapper } from '@sb-utils';
6
6
  import { SBCard } from '@sb-utils/SBCard.js';
7
- import type { CSSVariables } from '../../utils/types.js';
8
-
9
- const removeUnderlineStyle: CSSVariables = {
10
- '--hscl-link-textDecoration': 'none',
11
- '--hscl-link-textDecoration-hover': 'none',
12
- };
13
7
 
14
8
  const meta: Meta<LinkProps> = {
15
9
  title: 'Component Library/Link',
@@ -34,7 +28,7 @@ export const Default: Story = {
34
28
  href: 'https://www.hubspot.com',
35
29
  target: '_blank',
36
30
  rel: 'noopener noreferrer',
37
- variant: 'primary',
31
+ variant: 'primaryLink',
38
32
  className: 'test-class',
39
33
  },
40
34
  };
@@ -90,7 +84,7 @@ export const InContext: Story = {
90
84
  gap="medium"
91
85
  style={{ flexWrap: 'wrap' }}
92
86
  >
93
- <Link href="https://www.hubspot.com" style={removeUnderlineStyle}>
87
+ <Link href="https://www.hubspot.com">
94
88
  <SBCard
95
89
  title="Clickable Card"
96
90
  description="The entire card is wrapped in a link, making it clickable."
@@ -143,7 +137,6 @@ export const InContext: Story = {
143
137
  <Link
144
138
  key={key}
145
139
  href={`/image/${key}`}
146
- style={removeUnderlineStyle}
147
140
  >
148
141
  <div
149
142
  style={{
@@ -179,7 +172,7 @@ export const InteractionStates: Story = {
179
172
  <SBContainer addBackground>
180
173
  <h4>Hover State</h4>
181
174
  <p>Hover over the link to see the hover effect</p>
182
- <Link href="https://www.hubspot.com" variant="primary">
175
+ <Link href="https://www.hubspot.com" variant="primaryLink">
183
176
  Hover me
184
177
  </Link>
185
178
  </SBContainer>
@@ -192,7 +185,7 @@ export const InteractionStates: Story = {
192
185
  to show how it looks.
193
186
  </p>
194
187
  <SBFocusWrapper>
195
- <Link href="https://www.hubspot.com" variant="primary">
188
+ <Link href="https://www.hubspot.com" variant="primaryLink">
196
189
  Focused link
197
190
  </Link>
198
191
  </SBFocusWrapper>
@@ -0,0 +1,15 @@
1
+ .linkContainer {
2
+ [data-link-variant='primaryLink'] {
3
+ --hs-link-color: #0066cc;
4
+ --hs-link-textDecoration: none;
5
+ --hs-link-color-hover: #0052a3;
6
+ --hs-link-textDecoration-hover: underline;
7
+ }
8
+
9
+ [data-link-variant='secondaryLink'] {
10
+ --hs-link-color: #516f90;
11
+ --hs-link-textDecoration: none;
12
+ --hs-link-color-hover: #33475b;
13
+ --hs-link-textDecoration-hover: underline;
14
+ }
15
+ }
@@ -1,17 +1,8 @@
1
1
  import type { Decorator } from '@storybook/react';
2
- import type { CSSVariables } from '../../utils/types.js';
3
-
4
- const defaultLinkStyles: CSSVariables = {
5
- '--hscl-link-color-primary': '#0066cc',
6
- '--hscl-link-color-secondary': '#516f90',
7
- '--hscl-link-color-tertiary': '#7c98b6',
8
- '--hscl-link-color-hover-primary': '#0052a3',
9
- '--hscl-link-color-hover-secondary': '#33475b',
10
- '--hscl-link-color-hover-tertiary': '#516f90',
11
- };
2
+ import styles from './LinkDecorator.module.scss';
12
3
 
13
4
  export const withLinkStyles: Decorator = Story => (
14
- <div style={defaultLinkStyles}>
5
+ <div className={styles.linkContainer}>
15
6
  <Story />
16
7
  </div>
17
8
  );
@@ -1,8 +1,10 @@
1
1
  import { LinkFieldDefaults, Visibility } from '@hubspot/cms-components/fields';
2
2
  import { LinkFieldValue } from '../utils/linkField.js';
3
3
 
4
+ export type LinkVariant = 'primaryLink' | 'secondaryLink';
5
+
4
6
  export type BaseLinkProps = {
5
- variant?: 'primary' | 'secondary' | 'tertiary';
7
+ variant?: LinkVariant;
6
8
  className?: string;
7
9
  style?: React.CSSProperties;
8
10
  children?: React.ReactNode;
@@ -42,7 +44,7 @@ export type ContentFieldsProps<TLinkName extends string = 'link'> = {
42
44
  );
43
45
 
44
46
  export type StyleFieldsProps = {
45
- linkVariantLabel?: string;
46
- linkVariantName?: string;
47
- linkVariantDefault?: string;
47
+ variantLabel?: string;
48
+ variantName?: string;
49
+ variantDefault?: { variant_name: LinkVariant };
48
50
  };
@@ -136,8 +136,11 @@ render: () => {
136
136
  property: var(--hscl-componentName-property-hover);
137
137
  }
138
138
 
139
- &:focus {
140
- property: var(--hscl-componentName-property-focus);
139
+ // Focus-visible reuses hover styles + browser default outline
140
+ &:focus-visible {
141
+ property: var(--hscl-componentName-property-hover);
142
+ outline: 2px solid Highlight;
143
+ outline-offset: 2px;
141
144
  }
142
145
 
143
146
  &:disabled {
@@ -186,9 +189,11 @@ Use `&` for pseudo-classes and nested selectors:
186
189
  background-color: var(--hscl-button-backgroundColor-hover);
187
190
  }
188
191
 
189
- &:focus {
190
- outline: var(--hscl-button-outlineWidth-focus) solid
191
- var(--hscl-button-outlineColor-focus);
192
+ &:focus-visible {
193
+ background-color: var(--hscl-button-backgroundColor-hover);
194
+ color: var(--hscl-button-color-hover);
195
+ outline: 2px solid Highlight;
196
+ outline-offset: 2px;
192
197
  }
193
198
 
194
199
  &:disabled {
@@ -244,9 +249,11 @@ Use `&` for pseudo-classes and nested selectors:
244
249
  background-color: var(--hscl-button-backgroundColor-hover);
245
250
  }
246
251
 
247
- &:focus {
248
- outline: var(--hscl-button-outlineWidth-focus) solid
249
- var(--hscl-button-outlineColor-focus);
252
+ &:focus-visible {
253
+ background-color: var(--hscl-button-backgroundColor-hover);
254
+ color: var(--hscl-button-color-hover);
255
+ outline: 2px solid Highlight;
256
+ outline-offset: 2px;
250
257
  }
251
258
 
252
259
  &:disabled {
@@ -286,10 +293,12 @@ All interactive components should style **hover, focus, active** states:
286
293
  color: var(--hscl-button-color-hover, var(--hscl-button-color));
287
294
  }
288
295
 
289
- // Focus-visible - keyboard navigation only (not mouse clicks)
296
+ // Focus-visible - reuses hover styles + browser default outline
290
297
  &:focus-visible {
291
- outline: var(--hscl-button-outlineWidth-focus, 2px) solid var(--hscl-button-outlineColor-focus, currentColor);
292
- outline-offset: var(--hscl-button-outlineOffset-focus, 2px);
298
+ background-color: var(--hscl-button-backgroundColor-hover, var(--hscl-button-backgroundColor));
299
+ color: var(--hscl-button-color-hover, var(--hscl-button-color));
300
+ outline: 2px solid Highlight;
301
+ outline-offset: 2px;
293
302
  }
294
303
 
295
304
  // Active - pressed/clicked state
@@ -322,11 +331,17 @@ All interactive components should style **hover, focus, active** states:
322
331
  background-color: var(--hscl-button-backgroundColor-hover);
323
332
  ```
324
333
 
325
- 3. **Use `currentColor` for focus outlines**
326
- - Adapts to component's text color automatically
327
- - Ensures sufficient contrast in most cases
334
+ 3. **Reuse hover styles for focus, rely on browser default outline**
335
+ - Focus-visible should apply the same visual treatment as hover (background, color, border)
336
+ - Use `outline: 2px solid initial` for the browser's default focus ring color
337
+ - No need for separate focus-specific CSS variables
328
338
  ```scss
329
- outline: var(--hscl-button-outlineColor-focus, currentColor);
339
+ &:focus-visible {
340
+ background-color: var(--hscl-button-backgroundColor-hover, var(--hscl-button-backgroundColor));
341
+ color: var(--hscl-button-color-hover, var(--hscl-button-color));
342
+ outline: 2px solid Highlight;
343
+ outline-offset: 2px;
344
+ }
330
345
  ```
331
346
 
332
347
  4. **Respect reduced motion preferences**
@@ -360,8 +375,10 @@ See these components for implementation examples:
360
375
  property: var(--hscl-componentName-property-hover);
361
376
  }
362
377
 
363
- &:focus {
364
- property: var(--hscl-componentName-property-focus);
378
+ &:focus-visible {
379
+ property: var(--hscl-componentName-property-hover);
380
+ outline: 2px solid Highlight;
381
+ outline-offset: 2px;
365
382
  }
366
383
  }
367
384
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cms-component-library",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "HubSpot CMS React component library for building CMS modules",
5
5
  "license": "Apache-2.0",
6
6
  "exports": {