Repository: mui/material-ui (TypeScript)
Date: 2018-11-03 12:35:27+00:00
Task type: Feature

Issue:
[FormControlLabel] Add labelPlacement?: 'top' | 'bottom'
Currently the FormControlLabel only allows the label to appear on the right of the Control (Switch, Radio, Checkbox). It would be helpful to provide a property to position the Label on the Top, Right and Left.



Issue discussion:
I'm closing as a duplicate of #11618
@oliviertassinari I don't think it's really a duplicate. I'm looking to add a label top on a Checkbox, and it seems not possible.

ex:
![image](https://user-images.githubusercontent.com/3756105/47730413-638eaa80-dc62-11e8-8c91-1b12895ced0c.png)

@JulienMalige Sure, we can add the`top` and `bottom` variants. Thank you for raising 👍 .
I'm assuming that the checkbox and label would be center aligned (in the case of a top or bottom label) and certainly in the case of a top label it should be aligned "vertically" with other control labels so that if one places multiple controls on a row, the labels all align. 
@serle Yes, I think too.

Solution patch:
diff --git a/docs/src/pages/demos/selection-controls/FormControlLabelPosition.js b/docs/src/pages/demos/selection-controls/FormControlLabelPosition.js
new file mode 100644
--- /dev/null
+++ b/docs/src/pages/demos/selection-controls/FormControlLabelPosition.js
@@ -0,0 +1,58 @@
+import React from 'react';
+import Radio from '@material-ui/core/Radio';
+import RadioGroup from '@material-ui/core/RadioGroup';
+import FormControlLabel from '@material-ui/core/FormControlLabel';
+import FormControl from '@material-ui/core/FormControl';
+import FormLabel from '@material-ui/core/FormLabel';
+
+class FormControlLabelPosition extends React.Component {
+  state = {
+    value: 'female',
+  };
+
+  handleChange = event => {
+    this.setState({ value: event.target.value });
+  };
+
+  render() {
+    return (
+      <FormControl component="fieldset">
+        <FormLabel component="legend">labelPlacement</FormLabel>
+        <RadioGroup
+          aria-label="position"
+          name="position"
+          value={this.state.value}
+          onChange={this.handleChange}
+          row
+        >
+          <FormControlLabel
+            value="top"
+            control={<Radio color="primary" />}
+            label="Top"
+            labelPlacement="top"
+          />
+          <FormControlLabel
+            value="left"
+            control={<Radio color="primary" />}
+            label="Left"
+            labelPlacement="left"
+          />
+          <FormControlLabel
+            value="bottom"
+            control={<Radio color="primary" />}
+            label="Bottom"
+            labelPlacement="bottom"
+          />
+          <FormControlLabel
+            value="right"
+            control={<Radio color="primary" />}
+            label="Right"
+            labelPlacement="right"
+          />
+        </RadioGroup>
+      </FormControl>
+    );
+  }
+}
+
+export default FormControlLabelPosition;
diff --git a/docs/src/pages/demos/selection-controls/selection-controls.md b/docs/src/pages/demos/selection-controls/selection-controls.md
--- a/docs/src/pages/demos/selection-controls/selection-controls.md
+++ b/docs/src/pages/demos/selection-controls/selection-controls.md
@@ -85,3 +85,9 @@ If you have been reading the [overrides documentation page](/customization/overr
 but you are not confident jumping in, here's an example of how you can change the color of a Switch, and an iOS style Switch.
 
 {{"demo": "pages/demos/selection-controls/CustomizedSwitches.js"}}
+
+## Label placement
+
+You can change the placement of the label:
+
+{{"demo": "pages/demos/selection-controls/FormControlLabelPosition.js"}}
diff --git a/packages/material-ui/src/FormControlLabel/FormControlLabel.d.ts b/packages/material-ui/src/FormControlLabel/FormControlLabel.d.ts
--- a/packages/material-ui/src/FormControlLabel/FormControlLabel.d.ts
+++ b/packages/material-ui/src/FormControlLabel/FormControlLabel.d.ts
@@ -14,7 +14,7 @@ export interface FormControlLabelProps
   label: React.ReactNode;
   name?: string;
   onChange?: (event: React.ChangeEvent<{}>, checked: boolean) => void;
-  labelPlacement?: 'end' | 'start';
+  labelPlacement?: 'end' | 'start' | 'top' | 'bottom';
   value?: string;
 }
 
diff --git a/packages/material-ui/src/FormControlLabel/FormControlLabel.js b/packages/material-ui/src/FormControlLabel/FormControlLabel.js
--- a/packages/material-ui/src/FormControlLabel/FormControlLabel.js
+++ b/packages/material-ui/src/FormControlLabel/FormControlLabel.js
@@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
 import classNames from 'classnames';
 import withStyles from '../styles/withStyles';
 import Typography from '../Typography';
+import { capitalize } from '../utils/helpers';
 
 export const styles = theme => ({
   /* Styles applied to the root element. */
@@ -28,6 +29,16 @@ export const styles = theme => ({
     marginLeft: 16, // used for row presentation of radio/checkbox
     marginRight: -14,
   },
+  /* Styles applied to the root element if `labelPlacement="top"`. */
+  labelPlacementTop: {
+    flexDirection: 'column-reverse',
+    marginLeft: 16,
+  },
+  /* Styles applied to the root element if `labelPlacement="bottom"`. */
+  labelPlacementBottom: {
+    flexDirection: 'column',
+    marginLeft: 16,
+  },
   /* Styles applied to the root element if `disabled={true}`. */
   disabled: {},
   /* Styles applied to the label's Typography component. */
@@ -81,7 +92,7 @@ function FormControlLabel(props, context) {
       className={classNames(
         classes.root,
         {
-          [classes.labelPlacementStart]: labelPlacement === 'start',
+          [classes[`labelPlacement${capitalize(labelPlacement)}`]]: labelPlacement !== 'end',
           [classes.disabled]: disabled,
         },
         classNameProp,
@@ -132,7 +143,7 @@ FormControlLabel.propTypes = {
   /**
    * The position of the label.
    */
-  labelPlacement: PropTypes.oneOf(['end', 'start']),
+  labelPlacement: PropTypes.oneOf(['end', 'start', 'top', 'bottom']),
   /*
    * @ignore
    */
diff --git a/packages/material-ui/src/utils/helpers.js b/packages/material-ui/src/utils/helpers.js
--- a/packages/material-ui/src/utils/helpers.js
+++ b/packages/material-ui/src/utils/helpers.js
@@ -1,5 +1,9 @@
 import warning from 'warning';
 
+// It should to be noted that this function isn't equivalent to `text-transform: capitalize`.
+//
+// A strict capitalization should uppercase the first letter of each word a the sentence.
+// We only handle the first word.
 export function capitalize(string) {
   if (process.env.NODE_ENV !== 'production' && typeof string !== 'string') {
     throw new Error('Material-UI: capitalize(string) expects a string argument.');
diff --git a/pages/api/form-control-label.md b/pages/api/form-control-label.md
--- a/pages/api/form-control-label.md
+++ b/pages/api/form-control-label.md
@@ -26,7 +26,7 @@ Use this component if you want to display an extra label.
 | <span class="prop-name">disabled</span> | <span class="prop-type">bool</span> |   | If `true`, the control will be disabled. |
 | <span class="prop-name">inputRef</span> | <span class="prop-type">union:&nbsp;func&nbsp;&#124;<br>&nbsp;object<br></span> |   | Use that property to pass a ref callback to the native input component. |
 | <span class="prop-name">label</span> | <span class="prop-type">node</span> |   | The text to be used in an enclosing label element. |
-| <span class="prop-name">labelPlacement</span> | <span class="prop-type">enum:&nbsp;'end'&nbsp;&#124;<br>&nbsp;'start'<br></span> | <span class="prop-default">'end'</span> | The position of the label. |
+| <span class="prop-name">labelPlacement</span> | <span class="prop-type">enum:&nbsp;'end'&nbsp;&#124;<br>&nbsp;'start'&nbsp;&#124;<br>&nbsp;'top'&nbsp;&#124;<br>&nbsp;'bottom'<br></span> | <span class="prop-default">'end'</span> | The position of the label. |
 | <span class="prop-name">name</span> | <span class="prop-type">string</span> |   |  |
 | <span class="prop-name">onChange</span> | <span class="prop-type">func</span> |   | Callback fired when the state is changed.<br><br>**Signature:**<br>`function(event: object, checked: boolean) => void`<br>*event:* The event source of the callback. You can pull out the new value by accessing `event.target.checked`.<br>*checked:* The `checked` value of the switch |
 | <span class="prop-name">value</span> | <span class="prop-type">string</span> |   | The value of the component. |
@@ -43,6 +43,8 @@ This property accepts the following keys:
 |:-----|:------------|
 | <span class="prop-name">root</span> | Styles applied to the root element.
 | <span class="prop-name">labelPlacementStart</span> | Styles applied to the root element if `labelPlacement="start"`.
+| <span class="prop-name">labelPlacementTop</span> | Styles applied to the root element if `labelPlacement="top"`.
+| <span class="prop-name">labelPlacementBottom</span> | Styles applied to the root element if `labelPlacement="bottom"`.
 | <span class="prop-name">disabled</span> | Styles applied to the root element if `disabled={true}`.
 | <span class="prop-name">label</span> | Styles applied to the label's Typography component.
 
diff --git a/pages/demos/selection-controls.js b/pages/demos/selection-controls.js
--- a/pages/demos/selection-controls.js
+++ b/pages/demos/selection-controls.js
@@ -73,6 +73,15 @@ module.exports = require('fs')
   .readFileSync(require.resolve(
     'docs/src/pages/demos/selection-controls/CustomizedSwitches'
   ), 'utf8')
+`,
+        },
+        'pages/demos/selection-controls/FormControlLabelPosition.js': {
+          js: require('docs/src/pages/demos/selection-controls/FormControlLabelPosition').default,
+          raw: preval`
+module.exports = require('fs')
+  .readFileSync(require.resolve(
+    'docs/src/pages/demos/selection-controls/FormControlLabelPosition'
+  ), 'utf8')
 `,
         },
       }}


Test patch:
diff --git a/packages/material-ui/src/FormControlLabel/FormControlLabel.test.js b/packages/material-ui/src/FormControlLabel/FormControlLabel.test.js
--- a/packages/material-ui/src/FormControlLabel/FormControlLabel.test.js
+++ b/packages/material-ui/src/FormControlLabel/FormControlLabel.test.js
@@ -56,12 +56,26 @@ describe('<FormControlLabel />', () => {
   });
 
   describe('prop: labelPlacement', () => {
-    it('should disable have the `start` class', () => {
+    it('should have the `start` class', () => {
       const wrapper = shallow(
         <FormControlLabel label="Pizza" labelPlacement="start" control={<div />} />,
       );
       assert.strictEqual(wrapper.hasClass(classes.labelPlacementStart), true);
     });
+
+    it('should have the `top` class', () => {
+      const wrapper = shallow(
+        <FormControlLabel label="Pizza" labelPlacement="top" control={<div />} />,
+      );
+      assert.strictEqual(wrapper.hasClass(classes.labelPlacementTop), true);
+    });
+
+    it('should have the `bottom` class', () => {
+      const wrapper = shallow(
+        <FormControlLabel label="Pizza" labelPlacement="bottom" control={<div />} />,
+      );
+      assert.strictEqual(wrapper.hasClass(classes.labelPlacementBottom), true);
+    });
   });
 
   it('should mount without issue', () => {