element', () => {
+ const wrapper = shallow(
+
+
+ ,
+ );
+ assert.strictEqual(wrapper.type(), 'div');
+ });
+
+ it('should render the custom className and the root class', () => {
+ const wrapper = shallow(
+
+
+ ,
+ );
+ assert.strictEqual(wrapper.is('.test-class-name'), true, 'should pass the test className');
+ assert.strictEqual(wrapper.hasClass(classes.root), true);
+ });
+
+ it('should render a selected div', () => {
+ const wrapper = shallow(
+
+
+ ,
+ );
+ assert.strictEqual(wrapper.hasClass(classes.root), true);
+ assert.strictEqual(wrapper.hasClass(classes.selected), true, 'should have the selected class');
+ });
+
+ it('should render a selected div when selected is "auto" and a value is present', () => {
+ const wrapper = shallow(
+
+
+ ,
+ );
+ assert.strictEqual(wrapper.hasClass(classes.root), true);
+ assert.strictEqual(wrapper.hasClass(classes.selected), true, 'should have the selected class');
+ });
+
+ it('should not render a selected div when selected is "auto" and a value is missing', () => {
+ const wrapper = shallow(
+
+
+ ,
+ );
+ assert.strictEqual(wrapper.hasClass(classes.root), true);
+ assert.strictEqual(
+ wrapper.hasClass(classes.selected),
+ false,
+ 'should not have the selected class',
+ );
+ });
+
+ describe('exclusive', () => {
+ it('should render a selected ToggleButton if value is selected', () => {
+ const wrapper = shallow(
+
+
+ ,
+ );
+ const buttonWrapper = wrapper.find(ToggleButton);
+
+ assert.strictEqual(buttonWrapper.props().selected, true);
+ });
+
+ it('should not render a selected ToggleButton when its value is not selected', () => {
+ const wrapper = shallow(
+
+
+
+ ,
+ );
+ const buttonWrapper = wrapper.find(ToggleButton).at(1);
+
+ assert.strictEqual(buttonWrapper.props().selected, false);
+ });
+ });
+
+ describe('non exclusive', () => {
+ it('should render a selected ToggleButton if value is selected', () => {
+ const wrapper = shallow(
+
+
+
+ ,
+ );
+
+ assert.strictEqual(
+ wrapper
+ .find(ToggleButton)
+ .at(0)
+ .props().selected,
+ true,
+ 'should be selected',
+ );
+ assert.strictEqual(
+ wrapper
+ .find(ToggleButton)
+ .at(1)
+ .props().selected,
+ false,
+ 'should not be selected',
+ );
+ });
+ });
+
+ describe('prop: onChange', () => {
+ describe('exclusive', () => {
+ it('should be null when current value is toggled off', () => {
+ const handleChange = spy();
+ const wrapper = mount(
+
+ One
+ Two
+ ,
+ );
+
+ wrapper
+ .find(ToggleButton)
+ .at(0)
+ .simulate('click');
+
+ assert.strictEqual(handleChange.callCount, 1);
+ assert.strictEqual(handleChange.args[0][0], null);
+ });
+
+ it('should be a single value when value is toggled on', () => {
+ const handleChange = spy();
+ const wrapper = mount(
+
+ One
+ Two
+ ,
+ );
+
+ wrapper
+ .find(ToggleButton)
+ .at(0)
+ .simulate('click');
+
+ assert.strictEqual(handleChange.callCount, 1);
+ assert.strictEqual(handleChange.args[0][0], 'one');
+ });
+
+ it('should be a single value when a new value is toggled on', () => {
+ const handleChange = spy();
+ const wrapper = mount(
+
+ One
+ Two
+ ,
+ );
+
+ wrapper
+ .find(ToggleButton)
+ .at(1)
+ .simulate('click');
+
+ assert.strictEqual(handleChange.callCount, 1);
+ assert.strictEqual(handleChange.args[0][0], 'two');
+ });
+ });
+
+ describe('non exclusive', () => {
+ it('should be null when current value is toggled off', () => {
+ const handleChange = spy();
+ const wrapper = mount(
+
+ One
+ Two
+ ,
+ );
+
+ wrapper
+ .find(ToggleButton)
+ .at(0)
+ .simulate('click');
+
+ assert.strictEqual(handleChange.callCount, 1);
+ assert.strictEqual(handleChange.args[0][0], null);
+ });
+
+ it('should be an array with a single value when value is toggled on', () => {
+ const handleChange = spy();
+ const wrapper = mount(
+
+ One
+ Two
+ ,
+ );
+
+ wrapper
+ .find(ToggleButton)
+ .at(0)
+ .simulate('click');
+
+ assert.strictEqual(handleChange.callCount, 1);
+ assert.deepEqual(handleChange.args[0][0], ['one']);
+ });
+
+ it('should be an array with a single value when a secondary value is toggled off', () => {
+ const handleChange = spy();
+ const wrapper = mount(
+
+ One
+ Two
+ ,
+ );
+
+ wrapper
+ .find(ToggleButton)
+ .at(0)
+ .simulate('click');
+
+ assert.strictEqual(handleChange.callCount, 1);
+ assert.deepEqual(handleChange.args[0][0], ['two']);
+ });
+
+ it('should be an array of all selected values when a second value is toggled on', () => {
+ const handleChange = spy();
+ const wrapper = mount(
+
+ One
+ Two
+ ,
+ );
+
+ wrapper
+ .find(ToggleButton)
+ .at(1)
+ .simulate('click');
+
+ assert.strictEqual(handleChange.callCount, 1);
+ assert.deepEqual(handleChange.args[0][0], ['one', 'two']);
+ });
+ });
+ });
+});
diff --git a/packages/material-ui-lab/src/ToggleButton/hasValue.js b/packages/material-ui-lab/src/ToggleButton/hasValue.js
new file mode 100644
index 00000000000000..68504ba0e561c8
--- /dev/null
+++ b/packages/material-ui-lab/src/ToggleButton/hasValue.js
@@ -0,0 +1,8 @@
+// Determines if the given toggle group value is present.
+export default function hasValue(value) {
+ if (Array.isArray(value)) {
+ return value.length > 0;
+ }
+
+ return !!value;
+}
diff --git a/packages/material-ui-lab/src/ToggleButton/hasValue.test.js b/packages/material-ui-lab/src/ToggleButton/hasValue.test.js
new file mode 100644
index 00000000000000..2ab9b6f35f51cb
--- /dev/null
+++ b/packages/material-ui-lab/src/ToggleButton/hasValue.test.js
@@ -0,0 +1,24 @@
+import { assert } from 'chai';
+import hasValue from './hasValue';
+
+describe('
hasValue', () => {
+ it('should be true for a scalar value', () => {
+ assert.strictEqual(hasValue('yep'), true);
+ });
+
+ it('should be true for a non-empty array', () => {
+ assert.strictEqual(hasValue(['got one']), true);
+ });
+
+ it('should be false for an empty array', () => {
+ assert.strictEqual(hasValue([]), false);
+ });
+
+ it('should be false for undefined', () => {
+ assert.strictEqual(hasValue(undefined), false);
+ });
+
+ it('should be false for null', () => {
+ assert.strictEqual(hasValue(null), false);
+ });
+});
diff --git a/packages/material-ui-lab/src/ToggleButton/index.d.ts b/packages/material-ui-lab/src/ToggleButton/index.d.ts
new file mode 100644
index 00000000000000..a7a15e9cd24fe3
--- /dev/null
+++ b/packages/material-ui-lab/src/ToggleButton/index.d.ts
@@ -0,0 +1,3 @@
+export { default } from './ToggleButton';
+export { default as ToggleButtonGroup } from './ToggleButtonGroup';
+export * from './ToggleButton';
diff --git a/packages/material-ui-lab/src/ToggleButton/index.js b/packages/material-ui-lab/src/ToggleButton/index.js
new file mode 100644
index 00000000000000..a7a15e9cd24fe3
--- /dev/null
+++ b/packages/material-ui-lab/src/ToggleButton/index.js
@@ -0,0 +1,3 @@
+export { default } from './ToggleButton';
+export { default as ToggleButtonGroup } from './ToggleButtonGroup';
+export * from './ToggleButton';
diff --git a/packages/material-ui-lab/src/ToggleButton/isValueSelected.js b/packages/material-ui-lab/src/ToggleButton/isValueSelected.js
new file mode 100644
index 00000000000000..068c06c00173f4
--- /dev/null
+++ b/packages/material-ui-lab/src/ToggleButton/isValueSelected.js
@@ -0,0 +1,13 @@
+// Determine if the toggle button value matches, or is contained in, the
+// candidate group value.
+export default function isValueSelected(value, candidate) {
+ if (candidate === undefined || value === undefined) {
+ return false;
+ }
+
+ if (Array.isArray(candidate)) {
+ return candidate.indexOf(value) >= 0;
+ }
+
+ return value === candidate;
+}
diff --git a/packages/material-ui-lab/src/ToggleButton/isValueSelected.test.js b/packages/material-ui-lab/src/ToggleButton/isValueSelected.test.js
new file mode 100644
index 00000000000000..143b7fbe8e3278
--- /dev/null
+++ b/packages/material-ui-lab/src/ToggleButton/isValueSelected.test.js
@@ -0,0 +1,40 @@
+import { assert } from 'chai';
+import isValueSelected from './isValueSelected';
+
+describe('
isValueSelected', () => {
+ it('is false when value is undefined', () => {
+ assert.strictEqual(isValueSelected(undefined, [undefined]), false);
+ });
+
+ it('is false when candidate is undefined', () => {
+ assert.strictEqual(isValueSelected('example', undefined), false);
+ });
+
+ describe('non exclusive', () => {
+ it('is true if candidate is contained in value', () => {
+ assert.strictEqual(isValueSelected('one', ['one']), true);
+ });
+
+ it('is false if value is not contained in candidate', () => {
+ assert.strictEqual(isValueSelected('one', ['two']), false);
+ });
+
+ it('is false if value is loosely contained in candidate', () => {
+ assert.strictEqual(isValueSelected('3', [3]), false);
+ });
+ });
+
+ describe('exclusive', () => {
+ it('is true if candidate strictly equals value', () => {
+ assert.strictEqual(isValueSelected('one', 'one'), true);
+ });
+
+ it('is false if candidate does not equal value', () => {
+ assert.strictEqual(isValueSelected('two', 'one'), false);
+ });
+
+ it('is false if candidate loosely equals value', () => {
+ assert.strictEqual(isValueSelected('3', 3), false);
+ });
+ });
+});
diff --git a/packages/material-ui-lab/src/index.js b/packages/material-ui-lab/src/index.js
index 4bd3a6e8c7fd2f..209e6bf1aac574 100644
--- a/packages/material-ui-lab/src/index.js
+++ b/packages/material-ui-lab/src/index.js
@@ -1,3 +1,6 @@
export { default as SpeedDial } from './SpeedDial';
export { default as SpeedDialAction } from './SpeedDialAction';
export { default as SpeedDialIcon } from './SpeedDialIcon';
+
+export { default as ToggleButton } from './ToggleButton/ToggleButton';
+export { default as ToggleButtonGroup } from './ToggleButton/ToggleButtonGroup';
diff --git a/pages/lab/api/toggle-button-group.js b/pages/lab/api/toggle-button-group.js
new file mode 100644
index 00000000000000..3c86f55502a6f0
--- /dev/null
+++ b/pages/lab/api/toggle-button-group.js
@@ -0,0 +1,10 @@
+import React from 'react';
+import withRoot from 'docs/src/modules/components/withRoot';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import markdown from './toggle-button-group.md';
+
+function Page() {
+ return
;
+}
+
+export default withRoot(Page);
diff --git a/pages/lab/api/toggle-button-group.md b/pages/lab/api/toggle-button-group.md
new file mode 100644
index 00000000000000..b68355d2134920
--- /dev/null
+++ b/pages/lab/api/toggle-button-group.md
@@ -0,0 +1,45 @@
+---
+filename: /packages/material-ui-lab/src/ToggleButton/ToggleButtonGroup.js
+title: ToggleButtonGroup API
+---
+
+
+
+# ToggleButtonGroup
+
+
The API documentation of the ToggleButtonGroup React component.
+
+
+
+## Props
+
+| Name | Type | Default | Description |
+|:-----|:-----|:--------|:------------|
+|
children * |
node | | The content of the button. |
+| classes | object | | Useful to extend the style applied to components. |
+| exclusive | bool | false | If `true` only allow one of the child ToggleButton values to be selected. |
+| onChange | func | | Callback fired when the value changes.
**Signature:**
`function(event: object, value: object) => void`
*event:* The event source of the callback
*value:* of the selected buttons. When `exclusive` is true this is a single value; when false an array of selected values. |
+| selected | union: bool |
enum: 'auto'
| 'auto' | If `true` render the group in a selected state. If `auto` (the default) render in a selected state if `value` is not empty. |
+| value | any | null | The currently selected value within the group or an array of selected values when `exclusive` is false. |
+
+Any other properties supplied will be spread to the root element (native element).
+
+## CSS API
+
+You can override all the class names injected by Material-UI thanks to the `classes` property.
+This property accepts the following keys:
+- `root`
+- `selected`
+
+Have a look at [overriding with classes](/customization/overrides#overriding-with-classes) section
+and the [implementation of the component](https://github.com/mui-org/material-ui/tree/master/packages/material-ui-lab/src/ToggleButton/ToggleButtonGroup.js)
+for more detail.
+
+If using the `overrides` key of the theme as documented
+[here](/customization/themes#customizing-all-instances-of-a-component-type),
+you need to use the following style sheet name: `MuiToggleButtonGroup`.
+
+## Demos
+
+- [Toggle Button](/lab/toggle-button)
+
diff --git a/pages/lab/api/toggle-button.js b/pages/lab/api/toggle-button.js
new file mode 100644
index 00000000000000..a8419ce2e4f01b
--- /dev/null
+++ b/pages/lab/api/toggle-button.js
@@ -0,0 +1,10 @@
+import React from 'react';
+import withRoot from 'docs/src/modules/components/withRoot';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import markdown from './toggle-button.md';
+
+function Page() {
+ return ;
+}
+
+export default withRoot(Page);
diff --git a/pages/lab/api/toggle-button.md b/pages/lab/api/toggle-button.md
new file mode 100644
index 00000000000000..8ab6917600947e
--- /dev/null
+++ b/pages/lab/api/toggle-button.md
@@ -0,0 +1,53 @@
+---
+filename: /packages/material-ui-lab/src/ToggleButton/ToggleButton.js
+title: ToggleButton API
+---
+
+
+
+# ToggleButton
+
+The API documentation of the ToggleButton React component.
+
+
+
+## Props
+
+| Name | Type | Default | Description |
+|:-----|:-----|:--------|:------------|
+| children * | node | | The content of the button. |
+| classes | object | | Useful to extend the style applied to components. |
+| disabled | bool | false | If `true`, the button will be disabled. |
+| disableFocusRipple | bool | false | If `true`, the keyboard focus ripple will be disabled. `disableRipple` must also be true. |
+| disableRipple | bool | false | If `true`, the ripple effect will be disabled. |
+| selected | bool | | If `true`, the button will be rendered in an active state. |
+| value * | any | | The value to associate with the button when selected in a ToggleButtonGroup. |
+
+Any other properties supplied will be spread to the root element ([ButtonBase](/api/button-base)).
+
+## CSS API
+
+You can override all the class names injected by Material-UI thanks to the `classes` property.
+This property accepts the following keys:
+- `root`
+- `label`
+- `disabled`
+- `selected`
+
+Have a look at [overriding with classes](/customization/overrides#overriding-with-classes) section
+and the [implementation of the component](https://github.com/mui-org/material-ui/tree/master/packages/material-ui-lab/src/ToggleButton/ToggleButton.js)
+for more detail.
+
+If using the `overrides` key of the theme as documented
+[here](/customization/themes#customizing-all-instances-of-a-component-type),
+you need to use the following style sheet name: `MuiToggleButton`.
+
+## Inheritance
+
+The properties of the [ButtonBase](/api/button-base) component are also available.
+You can take advantage of this behavior to [target nested components](/guides/api#spread).
+
+## Demos
+
+- [Toggle Button](/lab/toggle-button)
+
diff --git a/pages/lab/toggle-button.js b/pages/lab/toggle-button.js
new file mode 100644
index 00000000000000..b10bf37e8dfd61
--- /dev/null
+++ b/pages/lab/toggle-button.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import withRoot from 'docs/src/modules/components/withRoot';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import markdown from 'docs/src/pages/lab/toggle-button/toggle-button.md';
+
+function Page() {
+ return (
+
+ );
+}
+
+export default withRoot(Page);