diff --git a/src/components/buttons/UnstyledButton.chromatic.stories.tsx b/src/components/buttons/UnstyledButton.chromatic.stories.tsx
new file mode 100644
index 0000000000..538b6837fa
--- /dev/null
+++ b/src/components/buttons/UnstyledButton.chromatic.stories.tsx
@@ -0,0 +1,7 @@
+import * as UnstyledButton from './UnstyledButton.stories.mdx';
+import {generateChromaticStory} from '../../chromatic/utils';
+
+export const Default = generateChromaticStory(UnstyledButton);
+const {includeStories, ...meta} = UnstyledButton.default;
+
+export default meta;
diff --git a/src/components/buttons/UnstyledButton.spec.tsx b/src/components/buttons/UnstyledButton.spec.tsx
new file mode 100644
index 0000000000..80e767e8a6
--- /dev/null
+++ b/src/components/buttons/UnstyledButton.spec.tsx
@@ -0,0 +1,79 @@
+import * as React from 'react';
+import UnstyledButton from './UnstyledButton';
+import {render} from '@testing-library/react';
+import {testA11y} from '../../axe';
+import userEvent from '@testing-library/user-event';
+
+describe('UnstyledButton', () => {
+ it('has a button role and an accessible label', () => {
+ const button = render(Some text);
+
+ expect(button.getByRole('button', {name: 'Some text'})).toBeInTheDocument();
+ });
+
+ it('disabled', () => {
+ const button = render(Some text);
+
+ expect(button.getByRole('button').hasAttribute('disabled')).toEqual(true);
+ });
+
+ it('is focusable', () => {
+ const button = render(Read more);
+
+ button.getByRole('button').focus();
+ expect(button.getByRole('button')).toBe(document.activeElement);
+ });
+
+ it('is not focusable and clickable when disabled', () => {
+ const handleOnClick = jest.fn();
+ const label = 'Load more';
+ const button = render(
+
+ {label}
+
+ );
+
+ button.getByText(label).focus();
+ expect(button.getByText(label)).not.toBe(document.activeElement);
+ userEvent.click(button.getByText(label));
+ expect(handleOnClick).not.toHaveBeenCalled();
+ });
+
+ it('fires onClick on click, space and enter', () => {
+ const handleOnClick = jest.fn();
+ const label = 'Load more';
+ const button = render(
+ {label}
+ );
+
+ userEvent.click(
+ button.getByRole('button', {
+ name: label,
+ })
+ );
+ expect(handleOnClick).toHaveBeenCalledTimes(1);
+ button.getByText(label).focus();
+ userEvent.keyboard('{space}');
+ expect(handleOnClick).toHaveBeenCalledTimes(2);
+ userEvent.keyboard('{enter}');
+ expect(handleOnClick).toHaveBeenCalledTimes(3);
+ });
+
+ describe('a11y', () => {
+ it('should have no a11y violations', async () => {
+ await testA11y(Read more);
+ });
+
+ it('should have no a11y violations when aria-label is provided', async () => {
+ await testA11y(
+
+ Read more
+
+ );
+ });
+
+ it('should have no a11y violations when disabled', async () => {
+ await testA11y(Read more);
+ });
+ });
+});
diff --git a/src/components/buttons/UnstyledButton.stories.mdx b/src/components/buttons/UnstyledButton.stories.mdx
new file mode 100644
index 0000000000..1906f36139
--- /dev/null
+++ b/src/components/buttons/UnstyledButton.stories.mdx
@@ -0,0 +1,93 @@
+import {ArgsTable, Meta, Story, Canvas} from '@storybook/addon-docs';
+import UnstyledButton from './UnstyledButton';
+import {styled} from '@storybook/theming';
+import Headline from '../text/Headline';
+import Label from '../labels/Label';
+import PageHeader from 'blocks/PageHeader';
+import UnstyledButtonA11y from './stories/UnstyledButton.a11y.mdx';
+
+
+
+UnstyledButton
+
+- [Stories](#stories)
+- [Accessibility](#accessibility)
+
+## Overview
+
+
+
+
+
+## Stories
+
+### Interactive box
+
+
+
+### User Profile Badge
+
+
+
+## Accessibility
+
+
diff --git a/src/components/buttons/UnstyledButton.tsx b/src/components/buttons/UnstyledButton.tsx
new file mode 100644
index 0000000000..00aa869d29
--- /dev/null
+++ b/src/components/buttons/UnstyledButton.tsx
@@ -0,0 +1,15 @@
+import * as React from 'react';
+import cx from 'classnames';
+
+export type UnstyledButtonPropsType = {
+ className?: string;
+} & React.ButtonHTMLAttributes;
+
+const UnstyledButton = (props: UnstyledButtonPropsType) => {
+ const {className, ...rest} = props;
+ const buttonClass = cx('sg-button-unstyled', className);
+
+ return ;
+};
+
+export default UnstyledButton;
diff --git a/src/components/buttons/_buttons_unstyled.scss b/src/components/buttons/_buttons_unstyled.scss
new file mode 100644
index 0000000000..5a2ec807ad
--- /dev/null
+++ b/src/components/buttons/_buttons_unstyled.scss
@@ -0,0 +1,7 @@
+.sg-button-unstyled {
+ appearance: none;
+ border: none;
+ background: none;
+ padding: 0;
+ display: block;
+}
diff --git a/src/components/buttons/stories/Button.a11y.mdx b/src/components/buttons/stories/Button.a11y.mdx
index 68985dea9a..8430abe20c 100644
--- a/src/components/buttons/stories/Button.a11y.mdx
+++ b/src/components/buttons/stories/Button.a11y.mdx
@@ -11,7 +11,7 @@ import rules, {hrefRules, toggleRules} from './rules.a11y';
Element, that looks like a button, but cause the user agent to navigate to a
new resource is a link and should meet the accessibility requirements for the
- link. More about it ypu can read in{' '}
+ link. More about it you can read in{' '}
the Link accessibility documentation
diff --git a/src/components/buttons/stories/UnstyledButton.a11y.mdx b/src/components/buttons/stories/UnstyledButton.a11y.mdx
new file mode 100644
index 0000000000..c3d48363fc
--- /dev/null
+++ b/src/components/buttons/stories/UnstyledButton.a11y.mdx
@@ -0,0 +1,33 @@
+import {unstyledButtonRules} from './rules.a11y';
+
+### Rules
+
+
+
+
+ UnstyledButton can be used as a functional wrapper for any interactive
+ component that triggers an onClick action.
+
+
+
+ Custom styling should follow accessible styling & color contrast requirements.
+
+
+### Usage
+
+#### Code examples
+
+- as a Box wrapper
+
+```jsx
+
+
+
+ Expert answer in 15 min
+
+
+ Get quick Math expert help from $2/month
+
+
+
+```
diff --git a/src/components/buttons/stories/rules.a11y.ts b/src/components/buttons/stories/rules.a11y.ts
index 834978cf8a..add89640c5 100644
--- a/src/components/buttons/stories/rules.a11y.ts
+++ b/src/components/buttons/stories/rules.a11y.ts
@@ -97,6 +97,7 @@ export const hrefRules = [
tests: 'DONE',
},
];
+
export const toggleRules = [
{
pattern: 'Should have type button.',
@@ -110,4 +111,67 @@ export const toggleRules = [
tests: 'TO DO',
},
];
+
+export const unstyledButtonRules = [
+ {
+ pattern: 'Should have an accessible name.',
+ comment: `Name should be meaningful (ex. "Read more about vitamin C" instead of "Read more") and explain the action
+ (ex. "Search" instead of "Magnifying glass"). aria-label can be used to provide a name.`,
+ status: 'DONE',
+ tests: 'TO DO',
+ },
+ {
+ pattern: 'Should have a role button.',
+ status: 'DONE',
+ tests: 'TO DO',
+ },
+ {
+ pattern: 'Should be focusable and tabable.',
+ status: 'DONE',
+ tests: 'TO DO',
+ },
+ {
+ pattern: 'Should have cursor: default.',
+ status: 'DONE',
+ tests: 'N/A',
+ },
+ {
+ pattern:
+ 'Should fire onClick on Space/Enter press and mouse click.',
+ status: 'DONE',
+ tests: 'TO DO',
+ },
+ {
+ pattern: 'Should have a proper type.',
+ status: 'DONE',
+ tests: 'TO DO',
+ },
+ {
+ pattern: 'Should have a non-color indicator.',
+ comment: 'To be implemented by the developer',
+ status: 'N/A',
+ tests: 'N/A',
+ },
+ {
+ pattern:
+ 'Should have a color indicator with 4.5:1 contrast ratio to the background.',
+ comment: 'To be implemented by the developer',
+ status: 'N/A',
+ tests: 'N/A',
+ },
+ {
+ pattern:
+ 'Should have a color indicator with 3:1 contrast ratio to the surrounding background.',
+ comment: 'To be implemented by the developer',
+ status: 'N/A',
+ tests: 'N/A',
+ },
+ {
+ pattern: 'Should have a visible disabled state.',
+ comment: 'To be implemented by the developer',
+ status: 'N/A',
+ tests: 'N/A',
+ },
+];
+
export default rules;
diff --git a/src/docs/blocks/InfoBox.tsx b/src/docs/blocks/InfoBox.tsx
index 9b401924a0..33129a7971 100644
--- a/src/docs/blocks/InfoBox.tsx
+++ b/src/docs/blocks/InfoBox.tsx
@@ -39,7 +39,7 @@ const ICONS_MAP: {
export const InfoBox = ({children, type = 'info'}: InfoBoxProps) => {
return (
-
+