Repository: uber-web/baseui
Pull request #125 (2018-08-14 00:32:13)

feat: form control component that adds label and caption
Implements the `FormControl` component for adding label and caption to form elements.

- [x] Main component
- [x] Exported styled components
- [x] Unit tests
- [x] Examples for storybook
- [x] Design review

schnerd: @nguyensomniac do we have designs for what a "required" form item looks like? (asterisk, red dot, etc).
nguyensomniac: Nailed the presentation, great job! ✨

Could you add an example for the `htmlFor` prop for labels? Wanted to make sure it works as expected.

Also, will we be able to use these for individual control labels for checkboxes and radios too?
nadiia: @nguyensomniac I added a story example for all controls we currently have: input, textarea, checkbox and radio group. I'll add one for a custom id/htmlFor prop.
nguyensomniac: Yup—but looks like the labels inside the individual components are unchanged. If we can use `StyledLabel` from here for those labels I'd advocate for those being the default.

Also, any way we could have the behavior that HTML `for` enables [(see example)](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label) as the default when you use these for labels/textareas/the like?
nguyensomniac: Talking about the ones in red, btw.

![frame 15](https://user-images.githubusercontent.com/6953488/44226574-5246ff00-a145-11e8-8296-b65505238071.png)


Patch:
diff --git a/package.json b/package.json
index 606e07b56..dc8046b02 100644
--- a/package.json
+++ b/package.json
@@ -1,18 +1,19 @@
 {
   "name": "baseui",
-  "version": "1.3.0",
+  "version": "1.3.1",
   "description": "React implementation of uber design components platform.",
   "keywords": [
     "react",
     "uber",
-    "base-ui"
+    "baseui"
   ],
   "license": "MIT",
   "main": "baseui.js",
   "module": "baseui.es.js",
   "repository": "uber-web/baseui",
   "scripts": {
-    "lint": "eslint ./ && markdownlint -c ./.markdownlint.json rfcs README.md",
+    "lintcode": "eslint ./",
+    "lint": "yarn run lintcode && markdownlint -c ./.markdownlint.json rfcs README.md",
     "commitmsg": "commitlint -e $GIT_PARAMS",
     "test": "yarn lint && yarn flow && yarn unit-test",
     "storybook": "start-storybook -p 6006",
diff --git a/rfcs/form-control-component.md b/rfcs/form-control-component.md
new file mode 100644
index 000000000..163e906b5
--- /dev/null
+++ b/rfcs/form-control-component.md
@@ -0,0 +1,62 @@
+# Form Control
+
+FormControl component adds label and caption for it's child control. If the error message is passed it renders error message in place of caption.
+
+## Usage
+
+```javascript
+import {FormControl} from 'baseui/form-control';
+import {Input} from 'baseui/input';
+import {Checkbox} from 'baseui/checkbox';
+
+export default () => {
+  return (
+    <div>
+      <FormControl label="Input label" caption="Input caption">
+        <Input />
+      </FormControl>
+      <FormControl label="Checkbox label" caption="Checkbox caption">
+        <Checkbox>Checkbox control</Checkbox>
+      </FormControl>
+      <FormControl
+        label="Input label"
+        caption="Input caption"
+        error="Error message"
+      >
+        <Input />
+      </FormControl>
+    </div>
+  );
+};
+```
+
+## Exports
+
+* `FormControl`
+* `StyledLabel`
+* `StyledCaption`
+* `StyledControlContainer`
+
+## Form Control API
+
+* `overrides: {Label, Caption, ControlContainer}` - Optional
+  Overrides for presentational components.
+  * `Label: {props: {}, style: {}, component: ReactComponent}` - Optional
+  * `Caption: {props: {}, style: {}, component: ReactComponent}` - Optional
+  * `ControlContainer: {props: {}, style: {}, component: ReactComponent}` - Optional
+* `label: node | function` - Optional
+  A label rendered above the input field.
+* `caption: node | function` - Optional
+  A caption rendered below the input field.
+* `error: node | function` - Optional
+  Error state of the input. If an error prop passed it will be rendered in place of caption as an error message.
+
+## Presentational components props API
+
+Next properties are passed to every presentational (styled) component that form control is composed of:
+
+* `$disabled: boolean`
+* `$error: boolean | node`
+* `$required: boolean`
+* `$size: 'default' | 'compact'`
+* `$theme: theme`
diff --git a/rfcs/icon-component.md b/rfcs/icon-component.md
new file mode 100644
index 000000000..058b3540d
--- /dev/null
+++ b/rfcs/icon-component.md
@@ -0,0 +1,69 @@
+# Icon Component
+
+This is the set of ~16 icons used internally within baseui that are safe to open source. This is not the set of uber-specific icons published internally as @uber/icons (though the API is intended to align with that to reduce confusion).
+
+![image](https://user-images.githubusercontent.com/875591/45131331-51e8c500-b141-11e8-898d-ead83d44b345.png)
+
+## Usage
+
+```javascript
+import * as React from 'react';
+import {LeftArrow} from 'baseui/icon/left-arrow';
+
+export default () => (
+  <LeftArrow size="10px" color="#ccc" />
+);
+```
+
+## Exports
+
+* `Icon` (Base Icon component used by other exports below)
+
+From left-to-right based on the image above:
+
+* `DeleteAlt`
+* `Search`
+* `Delete`
+* `Check`
+* `ArrowUp`
+* `ArrowDown`
+* `ArrowLeft`
+* `ArrowRight`
+* `TriangleRight`
+* `TriangleDown`
+* `Upload`
+* `Plus`
+* `Grab`
+* `Alert`
+* `Menu`
+* `ChevronRight`
+* `ChevronLeft`
+* `Spinner`
+* `Overflow`
+* `TriangleLeft`
+* `TriangleUp`
+* `Filter`
+* `CheckIndeterminate`
+
+## `Icon` API
+
+* `size: number|string` - Optional, Defaults to `scale500` (12px)
+  Size of element, will be passed to the svg width/height style. Can also be a baseui `theme.sizing` constant like `sizing500`.
+* `color: string` - Optional, Defaults to `currentColor`
+  Color of icon, will be used as svg fill
+* `title: string` - Optional
+  Allows you to override the SVG `<title>` label, which is used for accessibility
+* `overrides: {Svg}`
+  Override the implementation, styles, etc of the component internals.
+
+## Dependencies
+
+No dependencies
+
+## Accessibility
+
+Icons should have an `<title>` attribute in the SVG that describes the icon. If the icon is on a clickable element (button, etc) customer should add their own `aria-label` to that element.
+
+<https://simplyaccessible.com/article/7-solutions-svgs/> (See #2, #6)
+
+<https://www.w3.org/TR/svg-aam-1.0/>
\ No newline at end of file
diff --git a/rfcs/input-component.md b/rfcs/input-component.md
index 1cc834713..92f9d4ac5 100644
--- a/rfcs/input-component.md
+++ b/rfcs/input-component.md
@@ -48,12 +48,10 @@ export default () => {
 * `StatefulContainer`
 * `Input`
 * `BaseInput`
-* `StyledLabel`
 * `StyledRoot`
 * `StyledInputEnhancer`
 * `StyledInputContainer`
 * `StyledInput`
-* `StyledCaption`
 * `STATE_CHANGE_TYPE`
 * `ADJOINED`
 * `SIZE`
@@ -81,8 +79,8 @@ export default () => {
 ## Input component API
 
 * All properties of the BaseInput except `adjoined`
-* `overrides: {Label, Root, StartEnhancer, InputContainer, Input, Before, After, EndEnhancer, Caption}` - Optional
-  * `Label: ReactComponent` - Optional
+* `overrides: {Root, StartEnhancer, InputContainer, Input, Before, After, EndEnhancer}` - Optional
+  Overrides for presentational components.
   * `Root: ReactComponent` - Optional
   * `StartEnhancer: ReactComponent` - Optional
   * `InputContainer: ReactComponent` - Optional
@@ -90,12 +88,6 @@ export default () => {
   * `Input: ReactComponent` - Optional
   * `After: ReactComponent` - Optional
   * `EndEnhancer: ReactComponent` - Optional
-  * `Caption: ReactComponent` - Optional
-    Overrides for presentational components.
-* `label: node | function` - Optional
-  A label rendered above the input field.
-* `caption: node | function` - Optional
-  A caption rendered below the input field.
 * `startEnhancer: node | function` - Optional
   An input helper rendered before and attached to the input field.
 * `endEnhancer: node | function` - Optional
@@ -111,6 +103,8 @@ export default () => {
     Overrides for presentational components.
 * `id: string` - Optional
   Id attribute value to be added to the input element and as a label's `for` attribute value
+* `name: string` - Optional
+  Name attribute value.
 * `required: boolean` - Optional
   Indicates if the input is required and sets `aria-required` attribute
 * `type: string` - Optional
@@ -125,8 +119,8 @@ export default () => {
   A ref to access an input element.
 * `autoFocus: boolean` - Optional
   If `true` the input will be focused on the first mount.
-* `error: boolean | node | function` - Optional
-  Error state of the input. If a string or node element is passed it will be rendered in place of caption as an error message.
+* `error: boolean` - Optional
+  Error state of the input.
 * `adjoined: 'none' | 'left' | 'right' | 'both'`
   Defines styles for inputs that are grouped with other controls.
 * `size: 'default' | 'compact'`
@@ -144,7 +138,7 @@ Next properties are passed to every presentational (styled) component that input
 
 * `$isFocused: boolean`
 * `$disabled: boolean`
-* `$error: boolean | node`
+* `$error: boolean`
 * `$adjoined: 'none' | 'left' | 'right' | 'both'`
 * `$size: 'default' | 'compact'`
 * `$required: boolean`
diff --git a/rfcs/spinner-component.md b/rfcs/spinner-component.md
new file mode 100644
index 000000000..c88371730
--- /dev/null
+++ b/rfcs/spinner-component.md
@@ -0,0 +1,39 @@
+# Spinner
+
+## Usage
+
+### Basic usage
+
+```javascript
+import * as React from 'react';
+import {Spinner} from 'baseui/spinner';
+
+export default () => {
+  return
+    <Spinner
+      variant={Spinner.VARIANT.INDETERMINATE}
+      size={10}
+    />;
+};
+```
+
+## Exports
+
+* `Spinner`
+
+## `Spinner` API
+
+* `value: ?number` - Optional
+  The value of the progress indicator for the determinate and static variants. Value between `0` and `100`, defaults to `0`.
+* `size: ?number` - Optional
+  The size of the circle. Defaults to `44`.
+* `color: ?string` - Optional
+  The color to be used for the spinner
+* `backgroundColor: ?string` - Optional
+  The color to be used for the background circle
+
+## Accessibility
+
+The following attribute should be applied to the element: `role="progressbar"`. To learn more check out the [MDN ProgressBar Role] article
+
+[MDN ProgressBar Role]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_progressbar_role
diff --git a/src/form-control/constants.js b/src/form-control/constants.js
new file mode 100644
index 000000000..82b4703e5
--- /dev/null
+++ b/src/form-control/constants.js
@@ -0,0 +1,13 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+// Dictionary of props to prepend $ to pass through to styletron
+export const STYLETRON_PROP_MAPPER = {
+  disabled: true,
+  error: true,
+  required: true,
+  size: true,
+};
diff --git a/src/form-control/examples.js b/src/form-control/examples.js
new file mode 100644
index 000000000..e99dfa182
--- /dev/null
+++ b/src/form-control/examples.js
@@ -0,0 +1,181 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+import * as React from 'react';
+import {styled} from '../styles';
+import {FormControl, StyledLabel} from './';
+import {StatefulInput as Input, SIZE} from '../input';
+import {StatefulTextarea as Textarea} from '../textarea';
+import {StatefulCheckbox as Checkbox} from '../checkbox';
+import {StatefulRadioGroup as RadioGroup, StyledRadio} from '../radio';
+
+const InputIcon = styled('span', props => {
+  return {
+    width: '16px',
+    alignItems: 'center',
+    padding:
+      props.$position === 'left'
+        ? '0 0 0 12px'
+        : props.$position === 'right'
+          ? '0 12px 0 0'
+          : '0',
+    ':before': {
+      content: '""',
+      display: 'inline-block',
+      boxSizing: 'border-box',
+      verticalAlign: 'middle',
+      width: '16px',
+      height: '16px',
+      borderRadius: '50%',
+      backgroundColor: props.$isFocused
+        ? props.$theme.colors.primary
+        : '#999999',
+    },
+  };
+});
+
+const CustomLabel = ({children, ...rest}: {children: React.Node}) => {
+  return (
+    <StyledLabel {...rest} data-label="data-label">
+      {children}
+      <InputIcon $position="left" {...rest} />
+    </StyledLabel>
+  );
+};
+
+const Span = styled('span', {
+  display: 'flex',
+  alignItems: 'center',
+});
+const Icon = styled('span', ({$position, $error, $theme}) => {
+  return {
+    padding: $position === 'left' ? '0 8px 0 0' : '0 0 0 8px',
+    ':before': {
+      content: '""',
+      display: 'flex',
+      boxSizing: 'border-box',
+      verticalAlign: 'middle',
+      width: '16px',
+      height: '16px',
+      borderRadius: '50%',
+      backgroundColor: $error ? $theme.colors.negative400 : '#999999',
+    },
+  };
+});
+
+export const tests = {
+  CONTROLS_EXAMPLE: 'controls with label and caption',
+  BASE_EXAMPLE: 'label and caption props',
+};
+
+export default {
+  [tests.CONTROLS_EXAMPLE]: function example1() {
+    return (
+      <div>
+        <FormControl label="Input label" caption="Input caption">
+          <Input size={SIZE.compact} />
+        </FormControl>
+        <FormControl label="Textarea label" caption="Textarea caption">
+          <Textarea size={SIZE.compact} />
+        </FormControl>
+        <FormControl label="Checkbox label" caption="Checkbox caption">
+          <Checkbox>Checkbox control</Checkbox>
+        </FormControl>
+        <FormControl label="RadioGroup label" caption="RadioGroup caption">
+          <RadioGroup>
+            <StyledRadio value="red">Red</StyledRadio>
+            <StyledRadio value="green">Green</StyledRadio>
+            <StyledRadio value="blue">Blue</StyledRadio>
+          </RadioGroup>
+        </FormControl>
+      </div>
+    );
+  },
+  [tests.BASE_EXAMPLE]: function example1() {
+    return (
+      <div>
+        <FormControl label="String type label" caption="String type caption">
+          <Input size={SIZE.compact} />
+        </FormControl>
+        <FormControl
+          label={
+            <Span>
+              Element type label
+              <Icon $position="right" />
+            </Span>
+          }
+          caption={
+            <Span>
+              <Icon $position="left" />
+              Element type caption
+            </Span>
+          }
+        >
+          <Input size={SIZE.compact} />
+        </FormControl>
+        <FormControl
+          // $FlowFixMe
+          label={(props: {$disabled: boolean}) => {
+            const {$disabled} = props;
+            return `Function type label gets shared props.${
+              $disabled ? ' This input is disabled.' : ''
+            }`;
+          }}
+          // $FlowFixMe
+          caption={props => {
+            const {$disabled} = props;
+            return `Function type caption gets shared props.${
+              $disabled ? ' This input is disabled.' : ''
+            }`;
+          }}
+        >
+          <Input size={SIZE.compact} disabled />
+        </FormControl>
+        <FormControl label="Label" caption="Caption" error="String type error">
+          <Input size={SIZE.compact} />
+        </FormControl>
+        <FormControl
+          label="Label"
+          caption="Caption"
+          error={
+            <Span>
+              <Icon $position="left" $error />
+              Element type error
+            </Span>
+          }
+        >
+          <Input size={SIZE.compact} />
+        </FormControl>
+        <FormControl
+          label="Label"
+          caption="Caption"
+          // $FlowFixMe
+          error={(props: {$required: boolean}) => {
+            const {$required} = props;
+            return `Function type error gets shared props.${
+              $required ? ' This input is required.' : ''
+            }`;
+          }}
+        >
+          <Input size={SIZE.compact} required />
+        </FormControl>
+        <FormControl
+          label="Custom label"
+          caption="Caption with style overrides and extra props"
+          overrides={{
+            Label: {component: CustomLabel},
+            Caption: {
+              props: {'data-test-label': 'test'},
+              style: {color: '#00F'},
+            },
+          }}
+        >
+          <Input size={SIZE.compact} />
+        </FormControl>
+      </div>
+    );
+  },
+};
diff --git a/src/form-control/form-control.js b/src/form-control/form-control.js
new file mode 100644
index 000000000..96232b6bc
--- /dev/null
+++ b/src/form-control/form-control.js
@@ -0,0 +1,81 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+import * as React from 'react';
+import {getOverride, getOverrideProps} from '../helpers/overrides';
+import {STYLETRON_PROP_MAPPER} from './constants';
+import {
+  Label as StyledLabel,
+  Caption as StyledCaption,
+  ControlContainer as StyledControlContainer,
+} from './styled-components';
+import type {FormControlPropsT} from './types';
+
+function mapStyletronProps(props: {}, mapper: {}) {
+  return Object.keys(props).reduce((newProps, propName) => {
+    const newName = mapper[propName] && `$${propName}`;
+    if (newName) {
+      newProps[newName] = props[propName];
+    }
+    return newProps;
+  }, {});
+}
+
+export default class FormControl extends React.Component<FormControlPropsT> {
+  static defaultProps = {
+    overrides: {},
+    label: null,
+    caption: null,
+    error: false,
+  };
+
+  render() {
+    const {
+      overrides: {Label: LabelOverride, Caption: CaptionOverride},
+      label,
+      caption,
+      error,
+      children,
+    } = this.props;
+
+    const onlyChildProps = React.Children.only(children).props;
+    const sharedProps = mapStyletronProps(
+      onlyChildProps,
+      STYLETRON_PROP_MAPPER,
+    );
+    sharedProps.$error = this.props.error || sharedProps.$error;
+    const Label = getOverride(LabelOverride) || StyledLabel;
+    const Caption = getOverride(CaptionOverride) || StyledCaption;
+
+    return (
+      <React.Fragment>
+        {label && (
+          <Label
+            htmlFor={onlyChildProps.id}
+            {...sharedProps}
+            {...getOverrideProps(LabelOverride)}
+          >
+            {typeof label === 'function' ? label(sharedProps) : label}
+          </Label>
+        )}
+        <StyledControlContainer>
+          {children}
+          {(caption || error) && (
+            <Caption {...sharedProps} {...getOverrideProps(CaptionOverride)}>
+              {error && typeof error !== 'boolean'
+                ? typeof error === 'function'
+                  ? error(sharedProps)
+                  : error
+                : typeof caption === 'function'
+                  ? caption(sharedProps)
+                  : caption}
+            </Caption>
+          )}
+        </StyledControlContainer>
+      </React.Fragment>
+    );
+  }
+}
diff --git a/src/form-control/index.js b/src/form-control/index.js
new file mode 100644
index 000000000..ec9447f81
--- /dev/null
+++ b/src/form-control/index.js
@@ -0,0 +1,12 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+export {default as FormControl} from './form-control';
+export {
+  Label as StyledLabel,
+  Caption as StyledCaption,
+  ControlContainer as StyledControlContainer,
+} from './styled-components';
diff --git a/src/form-control/stories.js b/src/form-control/stories.js
new file mode 100644
index 000000000..9d08677b2
--- /dev/null
+++ b/src/form-control/stories.js
@@ -0,0 +1,21 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+/* global module */
+import {storiesOf} from '@storybook/react';
+import {withReadme} from 'storybook-readme';
+//$FlowFixMe
+import FormControlReadme from '../../rfcs/form-control-component.md';
+import examples from './examples';
+
+//$FlowFixMe
+
+Object.entries(examples).forEach(([description, example]) =>
+  storiesOf('Form control', module)
+    .addDecorator(withReadme(FormControlReadme))
+    // $FlowFixMe
+    .add(description, example),
+);
diff --git a/src/form-control/styled-components.js b/src/form-control/styled-components.js
new file mode 100644
index 000000000..893f112b2
--- /dev/null
+++ b/src/form-control/styled-components.js
@@ -0,0 +1,60 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+import {styled} from '../styles';
+
+export const Label = styled('label', props => {
+  const {
+    $disabled,
+    $theme: {colors, sizing, typography},
+  } = props;
+  return {
+    ...typography.font350,
+    fontWeight: 500,
+    color: $disabled ? colors.mono700 : colors.mono1000,
+    cursor: $disabled ? 'not-allowed' : 'auto',
+    display: 'block',
+    paddingTop: '0',
+    paddingRight: '0',
+    paddingBottom: '0',
+    paddingLeft: '0',
+    marginTop: sizing.scale300,
+    marginRight: '0',
+    marginBottom: sizing.scale300,
+    marginLeft: '0',
+  };
+});
+
+export const Caption = styled('div', props => {
+  const {
+    $error,
+    $theme: {colors, sizing, typography},
+  } = props;
+  return {
+    ...typography.font200,
+    color:
+      $error && typeof $error !== 'boolean'
+        ? colors.negative400
+        : colors.mono800,
+    paddingTop: '0',
+    paddingRight: '0',
+    paddingBottom: '0',
+    paddingLeft: '0',
+    marginTop: sizing.scale300,
+    marginRight: '0',
+    marginBottom: sizing.scale300,
+    marginLeft: '0',
+  };
+});
+
+export const ControlContainer = styled('div', props => {
+  const {
+    $theme: {sizing},
+  } = props;
+  return {
+    marginBottom: sizing.scale600,
+  };
+});
diff --git a/src/form-control/types.js b/src/form-control/types.js
new file mode 100644
index 000000000..ee667efce
--- /dev/null
+++ b/src/form-control/types.js
@@ -0,0 +1,19 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+import * as React from 'react';
+import type {OverrideT} from '../helpers/overrides';
+
+export type FormControlPropsT = {
+  overrides: {
+    Label?: OverrideT<*>,
+    Caption?: OverrideT<*>,
+  },
+  label: ?(React.Node | ((props: {}) => React.Node)),
+  caption: ?(React.Node | ((props: {}) => React.Node)),
+  error: boolean | React.Node | ((props: {}) => React.Node),
+  children: React.Node,
+};
diff --git a/src/input/base-input.js b/src/input/base-input.js
index f483ed9b6..dd0507e7f 100644
--- a/src/input/base-input.js
+++ b/src/input/base-input.js
@@ -23,6 +23,7 @@ class BaseInput extends React.Component<BaseInputPropsT, InternalStateT> {
     disabled: false,
     error: false,
     id: getBuiId(),
+    name: '',
     inputRef: React.createRef(),
     onBlur: () => {},
     onChange: () => {},
@@ -61,6 +62,7 @@ class BaseInput extends React.Component<BaseInputPropsT, InternalStateT> {
       disabled,
       error,
       id,
+      name,
       inputRef,
       onChange,
       placeholder,
@@ -75,6 +77,7 @@ class BaseInput extends React.Component<BaseInputPropsT, InternalStateT> {
       'aria-required': required,
       disabled,
       id,
+      name,
       onChange,
       onFocus: this.onFocus,
       onBlur: this.onBlur,
diff --git a/src/input/index.js b/src/input/index.js
index 2cea1fafe..4acb9e2df 100644
--- a/src/input/index.js
+++ b/src/input/index.js
@@ -11,12 +11,10 @@ export {default as Input} from './input';
 export {default as BaseInput} from './base-input';
 // Styled elements
 export {
-  Label as StyledLabel,
   Root as StyledRoot,
   InputEnhancer as StyledInputEnhancer,
   InputContainer as StyledInputContainer,
   Input as StyledInput,
-  Caption as StyledCaption,
 } from './styled-components';
 
 export {
diff --git a/src/input/input.js b/src/input/input.js
index baaa38a48..5c126b537 100644
--- a/src/input/input.js
+++ b/src/input/input.js
@@ -12,10 +12,8 @@ import type {InputPropsT, InternalStateT, AdjoinedT} from './types';
 import {getSharedProps} from './utils';
 import BaseInput from './base-input';
 import {
-  Label as StyledLabel,
   Root as StyledRoot,
   InputEnhancer as StyledInputEnhancer,
-  Caption as StyledCaption,
 } from './styled-components';
 import {ADJOINED, ENHANCER_POSITION} from './constants';
 
@@ -24,14 +22,13 @@ class Input extends React.Component<InputPropsT, InternalStateT> {
     autoFocus: false,
     disabled: false,
     id: getBuiId(),
+    name: '',
     error: false,
     onBlur: () => {},
     onFocus: () => {},
     overrides: {},
     required: false,
     size: 'default',
-    label: null,
-    caption: null,
     startEnhancer: null,
     endEnhancer: null,
   };
@@ -55,80 +52,52 @@ class Input extends React.Component<InputPropsT, InternalStateT> {
   };
 
   render() {
-    const {label, caption, startEnhancer, endEnhancer, ...rest} = this.props;
+    const {startEnhancer, endEnhancer, ...rest} = this.props;
 
     const {
-      Label: LabelOverride,
       Root: RootOverride,
       StartEnhancer: StartEnhancerOverride,
       EndEnhancer: EndEnhancerOverride,
-      Caption: CaptionOverride,
     } = this.props.overrides;
 
-    const Label = getOverride(LabelOverride) || StyledLabel;
     const Root = getOverride(RootOverride) || StyledRoot;
     const StartEnhancer =
       getOverride(StartEnhancerOverride) || StyledInputEnhancer;
     const EndEnhancer = getOverride(EndEnhancerOverride) || StyledInputEnhancer;
-    const Caption = getOverride(CaptionOverride) || StyledCaption;
 
     const sharedProps = getSharedProps(this.props, this.state);
 
-    const error = this.props.error;
-
     return (
-      <React.Fragment>
-        {label && (
-          <Label
-            {...getOverrideProps(LabelOverride)}
+      <Root {...getOverrideProps(RootOverride)} {...sharedProps}>
+        {startEnhancer && (
+          <StartEnhancer
+            {...getOverrideProps(StartEnhancerOverride)}
             {...sharedProps}
-            htmlFor={this.props.id}
+            $position={ENHANCER_POSITION.start}
           >
-            {typeof label === 'function' ? label(sharedProps) : label}
-          </Label>
+            {typeof startEnhancer === 'function'
+              ? startEnhancer(sharedProps)
+              : startEnhancer}
+          </StartEnhancer>
         )}
-        <Root {...getOverrideProps(RootOverride)} {...sharedProps}>
-          {startEnhancer && (
-            <StartEnhancer
-              {...getOverrideProps(StartEnhancerOverride)}
-              {...sharedProps}
-              $position={ENHANCER_POSITION.start}
-            >
-              {typeof startEnhancer === 'function'
-                ? startEnhancer(sharedProps)
-                : startEnhancer}
-            </StartEnhancer>
-          )}
-          <BaseInput
-            {...rest}
-            adjoined={getAdjoinedProp(startEnhancer, endEnhancer)}
-            onFocus={this.onFocus}
-            onBlur={this.onBlur}
-          />
-          {endEnhancer && (
-            <EndEnhancer
-              {...getOverrideProps(EndEnhancerOverride)}
-              {...sharedProps}
-              $position={ENHANCER_POSITION.end}
-            >
-              {typeof endEnhancer === 'function'
-                ? endEnhancer(sharedProps)
-                : endEnhancer}
-            </EndEnhancer>
-          )}
-        </Root>
-        {(caption || error) && (
-          <Caption {...getOverrideProps(CaptionOverride)} {...sharedProps}>
-            {error && typeof error !== 'boolean'
-              ? typeof error === 'function'
-                ? error(sharedProps)
-                : error
-              : typeof caption === 'function'
-                ? caption(sharedProps)
-                : caption}
-          </Caption>
+        <BaseInput
+          {...rest}
+          adjoined={getAdjoinedProp(startEnhancer, endEnhancer)}
+          onFocus={this.onFocus}
+          onBlur={this.onBlur}
+        />
+        {endEnhancer && (
+          <EndEnhancer
+            {...getOverrideProps(EndEnhancerOverride)}
+            {...sharedProps}
+            $position={ENHANCER_POSITION.end}
+          >
+            {typeof endEnhancer === 'function'
+              ? endEnhancer(sharedProps)
+              : endEnhancer}
+          </EndEnhancer>
         )}
-      </React.Fragment>
+      </Root>
     );
   }
 }
diff --git a/src/input/stories.js b/src/input/stories.js
index 0dbe39254..1a68f03c4 100644
--- a/src/input/stories.js
+++ b/src/input/stories.js
@@ -10,16 +10,14 @@ import * as React from 'react';
 import {storiesOf} from '@storybook/react';
 import {withStyle} from 'styletron-react';
 import {styled} from '../styles';
-import {withProps} from '../helpers';
 import {withReadme} from 'storybook-readme';
 //$FlowFixMe
 import InputReadme from '../../rfcs/input-component.md';
 import {
   Input as ControlledInput,
   StatefulInput as Input,
-  StyledInputContainer,
   StyledInput,
-  StyledLabel,
+  StyledInputContainer,
   SIZE,
 } from './index';
 
@@ -91,29 +89,6 @@ const RootWithStyle = withStyle(StyledInputContainer, props => {
   };
 });
 
-const InputWithProps = withProps(StyledInput, {
-  'data-test': 'test',
-});
-
-const LabelWithProps = withProps(StyledLabel, {
-  'data-test-label': 'test',
-});
-
-const LabelWithStyle = withStyle(StyledLabel, ({$isFocused, $theme}) => {
-  return {
-    display: 'flex',
-    color: $isFocused ? $theme.colors.primary : $theme.colors.mono1000,
-  };
-});
-const CustomLabel = ({children, ...rest}: {children: React.Node}) => {
-  return (
-    <LabelWithStyle {...rest} data-label="data-label">
-      {children}
-      <InputIcon $position="left" {...rest} />
-    </LabelWithStyle>
-  );
-};
-
 const TextHighlight = styled('span', ({$theme}) => {
   return {color: $theme.colors.primary};
 });
@@ -126,185 +101,57 @@ storiesOf('Input', module)
   .add('Controlled and uncontrolled input', () => {
     return (
       <React.Fragment>
-        <ControlledInput
-          label="Controlled input"
-          caption="Caption"
-          placeholder="Placeholder"
-        />
-        <Input
-          label="Uncontrolled (stateful) input"
-          caption="Caption"
-          placeholder="Placeholder"
-        />
+        <ControlledInput placeholder="Controlled input" />
+        <br />
+        <Input placeholder="Uncontrolled (stateful) input" />
       </React.Fragment>
     );
   })
   .add('Input size', () => {
     return (
       <React.Fragment>
-        <Input
-          label="Default input"
-          caption="Caption"
-          placeholder="Placeholder"
-        />
-        <Input
-          label="Compact input"
-          caption="Caption"
-          size={SIZE.compact}
-          placeholder="Placeholder"
-        />
+        <Input placeholder="Default input" />
+        <br />
+        <Input size={SIZE.compact} placeholder="Compact input" />
       </React.Fragment>
     );
   })
   .add('Input state', () => {
     return (
       <React.Fragment>
+        <Input size={SIZE.compact} placeholder="Default input" />
+        <br />
         <Input
-          label="Default input"
-          caption="Caption"
-          size={SIZE.compact}
-          placeholder="Placeholder"
-        />
-        <Input
-          label="Initially focused input"
           initialState={{value: 'uber'}}
-          caption="Caption"
           autoFocus
           startEnhancer="@"
           endEnhancer=".com"
           size={SIZE.compact}
-          placeholder="Placeholder"
+          placeholder="Initially focused input"
         />
+        <br />
         <Input
-          label="Input in an error state"
-          caption="Caption"
           size={SIZE.compact}
-          error="Error in place of caption when $error is a node"
-          placeholder="Placeholder"
+          error
+          placeholder="Input in an error state"
         />
+        <br />
         <Input
-          label="Input with enhancers in an error state"
-          caption="Caption rendered when $error is a boolean"
           startEnhancer="@"
           endEnhancer=".com"
           size={SIZE.compact}
           error
-          placeholder="Placeholder"
-        />
-        <Input
-          label="Disabled input"
-          caption="Caption"
-          size={SIZE.compact}
-          disabled
-          placeholder="Placeholder"
+          placeholder="Input with enhancers in an error state"
         />
+        <br />
+        <Input size={SIZE.compact} disabled placeholder="Disabled input" />
+        <br />
         <Input
-          label="Disabled input with enhancers"
-          caption="Caption"
           startEnhancer="@"
           endEnhancer=".com"
           size={SIZE.compact}
           disabled
-          placeholder="Placeholder"
-        />
-      </React.Fragment>
-    );
-  })
-  .add('Input label and caption', () => {
-    const Span = styled('span', {
-      display: 'flex',
-      alignItems: 'center',
-    });
-    const Icon = styled('span', ({$position, $error, $theme}) => {
-      return {
-        padding: $position === 'left' ? '0 8px 0 0' : '0 0 0 8px',
-        ':before': {
-          content: '""',
-          display: 'flex',
-          boxSizing: 'border-box',
-          verticalAlign: 'middle',
-          width: '16px',
-          height: '16px',
-          borderRadius: '50%',
-          backgroundColor: $error ? $theme.colors.negative400 : '#999999',
-        },
-      };
-    });
-    return (
-      <React.Fragment>
-        <Input
-          label="String type label"
-          caption="String type caption"
-          size={SIZE.compact}
-          placeholder="Placeholder"
-        />
-        <Input
-          label={
-            <Span>
-              Element type label
-              <Icon $position="right" />
-            </Span>
-          }
-          caption={
-            <Span>
-              <Icon $position="left" />
-              Element type caption
-            </Span>
-          }
-          size={SIZE.compact}
-          placeholder="Placeholder"
-        />
-        <Input
-          // $FlowFixMe
-          label={props => {
-            const {$isFocused} = props;
-            return $isFocused
-              ? 'Function type label gets shared props'
-              : 'Function type label';
-          }}
-          // $FlowFixMe
-          caption={props => {
-            const {$isFocused} = props;
-            return $isFocused
-              ? 'Function type caption gets shared props'
-              : 'Function type caption';
-          }}
-          size={SIZE.compact}
-          placeholder="Placeholder"
-        />
-        <Input
-          label="Label"
-          caption="Caption"
-          error="String type error caption"
-          size={SIZE.compact}
-          placeholder="Placeholder"
-        />
-        <Input
-          label="Label"
-          caption="Caption"
-          error={
-            <Span>
-              <Icon $position="left" $error />
-              Element type error caption
-            </Span>
-          }
-          size={SIZE.compact}
-          placeholder="Placeholder"
-        />
-        <Input
-          label="Label"
-          caption="Caption"
-          // $FlowFixMe
-          error={props => {
-            const {$isFocused} = props;
-            return $isFocused ? (
-              <TextHighlight>Change error when focused</TextHighlight>
-            ) : (
-              'Function type error gets shared props'
-            );
-          }}
-          size={SIZE.compact}
-          placeholder="Placeholder"
+          placeholder="Disabled input with enhancers"
         />
       </React.Fragment>
     );
@@ -312,44 +159,30 @@ storiesOf('Input', module)
   .add('Input enhancers', () => {
     return (
       <React.Fragment>
+        <Input startEnhancer="@" placeholder="Input with a startEnhancer" />
+        <br />
+        <Input endEnhancer=".00" placeholder="Input with an endEnhancer" />
+        <br />
         <Input
-          label="Input with a startEnhancer"
-          caption="Caption"
-          startEnhancer="@"
-          placeholder="Start enhancer"
-        />
-        <Input
-          label="Input with an endEnhancer"
-          caption="Caption"
-          endEnhancer=".00"
-          placeholder="End enhancer"
-        />
-        <Input
-          label="Input with start and end enhancers"
-          caption="Caption"
           startEnhancer="@"
           endEnhancer=".00"
-          placeholder="Placeholder"
+          placeholder="Input with start and end enhancers"
         />
+        <br />
         <Input
-          label="Input with element type enhancers"
-          caption="Caption"
           startEnhancer={<InputIcon />}
           endEnhancer={<InputIcon />}
-          placeholder="Placeholder"
+          placeholder="Input with element type enhancers"
         />
+        <br />
         <Input
-          label="Input with function type enhancers"
-          caption="Shared props are passed into an enhancer func"
-          // $FlowFixMe
           startEnhancer={({$isFocused}) => {
             return $isFocused ? <TextHighlight>@</TextHighlight> : '@';
           }}
-          // $FlowFixMe
           endEnhancer={({$isFocused}) => {
             return $isFocused ? <TextHighlight>.00</TextHighlight> : '.00';
           }}
-          placeholder="Placeholder"
+          placeholder="Input with function type enhancers. Shared props are passed into an enhancer func"
         />
       </React.Fragment>
     );
@@ -447,58 +280,53 @@ storiesOf('Input', module)
     return (
       <React.Fragment>
         <Input
-          label="Input with a Before component"
           overrides={{
             Before: function Before(props) {
               return <InputIcon {...props} $position="left" />;
             },
           }}
-          placeholder="With a Before element"
+          placeholder="Input with a Before component"
         />
+        <br />
         <Input
-          label="Input with an After component"
           overrides={{
             After: function After(props) {
               return <InputIcon {...props} $position="right" />;
             },
           }}
-          placeholder="With an After element"
-        />
-      </React.Fragment>
-    );
-  })
-  .add('Input with style overrides and extra props passed', () => {
-    return (
-      <React.Fragment>
-        <Input
-          label="Input with style overrides"
-          overrides={{InputContainer: RootWithStyle}}
-          placeholder="With style overrides on the Root element"
-        />
-        <Input
-          label="Input with extra props"
-          overrides={{
-            Input: InputWithProps,
-            Label: LabelWithProps,
-          }}
-          placeholder="With a 'data-test' attrs passes to the input and label elements"
-        />
-      </React.Fragment>
-    );
-  })
-  .add('Input with custom components', () => {
-    return (
-      <React.Fragment>
-        <Input
-          label="Input with custom label"
-          overrides={{
-            Label: CustomLabel,
-          }}
-          placeholder="Placeholder"
+          placeholder="Input with an After component"
         />
       </React.Fragment>
     );
   })
+  .add(
+    'Input with a with custom component override, style overrides and extra props passed',
+    () => {
+      return (
+        <React.Fragment>
+          <Input
+            overrides={{InputContainer: {component: RootWithStyle}}}
+            autoFocus
+            placeholder="Input with a custom InputContainer override"
+          />
+          <br />
+          <Input
+            overrides={{
+              Input: {
+                props: {'data-test': 'test'},
+                style: {color: '#00F'},
+              },
+            }}
+            initialState={{
+              value:
+                "With a 'data-test' attrs passes to the input and text style override",
+            }}
+            placeholder="With a 'data-test' attrs passes to the input and text style override"
+          />
+        </React.Fragment>
+      );
+    },
+  )
   .add('Input with a ref', () => {
     const inputRef: {current: ?React.ElementRef<'input'>} = React.createRef();
     return (
diff --git a/src/input/styled-components.js b/src/input/styled-components.js
index e10e2acd8..6037b6ccc 100644
--- a/src/input/styled-components.js
+++ b/src/input/styled-components.js
@@ -62,49 +62,6 @@ export const Root = styled('div', props => {
   };
 });
 
-export const Label = styled('label', props => {
-  const {
-    $disabled,
-    $theme: {colors, sizing, typography},
-  } = props;
-  return {
-    ...typography.font350,
-    fontWeight: 500,
-    color: $disabled ? colors.mono700 : colors.mono1000,
-    display: 'block',
-    paddingTop: '0',
-    paddingRight: '0',
-    paddingBottom: '0',
-    paddingLeft: '0',
-    marginTop: sizing.scale300,
-    marginRight: '0',
-    marginBottom: sizing.scale300,
-    marginLeft: '0',
-  };
-});
-
-export const Caption = styled('div', props => {
-  const {
-    $error,
-    $theme: {colors, sizing, typography},
-  } = props;
-  return {
-    ...typography.font200,
-    color:
-      $error && typeof $error !== 'boolean'
-        ? colors.negative400
-        : colors.mono800,
-    paddingTop: '0',
-    paddingRight: '0',
-    paddingBottom: '0',
-    paddingLeft: '0',
-    marginTop: sizing.scale300,
-    marginRight: '0',
-    marginBottom: sizing.scale300,
-    marginLeft: '0',
-  };
-});
-
 export const InputEnhancer = styled('div', props => {
   const {
     $position,
diff --git a/src/input/types.js b/src/input/types.js
index b3754311d..5e7663c78 100644
--- a/src/input/types.js
+++ b/src/input/types.js
@@ -35,7 +35,7 @@ export type StateReducerT = (
 export type SharedPropsT = {
   $isFocused: boolean,
   $disabled: boolean,
-  $error: boolean | React.Node,
+  $error: boolean,
   $adjoined: AdjoinedT,
   $size: SizeT,
   $required: boolean,
@@ -54,18 +54,17 @@ export type BaseInputComponentsT = {
 
 export type InputComponentsT = BaseInputComponentsT & {
   Root?: OverrideT<*>,
-  Label?: OverrideT<*>,
   StartEnhancer?: OverrideT<*>,
   EndEnhancer?: OverrideT<*>,
-  Caption?: OverrideT<*>,
 };
 
 export type BaseInputPropsT = {
   adjoined: AdjoinedT,
   autoFocus: boolean,
   disabled: boolean,
-  error: boolean | React.Node | ((props: PropsT) => React.Node),
+  error: boolean,
   id: string,
+  name: string,
   inputRef: {current: ?React.ElementRef<'input'>},
   onBlur: (e: SyntheticEventT) => void,
   onChange: (e: SyntheticEventT) => void,
@@ -82,8 +81,6 @@ export type BaseInputPropsT = {
 export type InputPropsT = {
   ...BaseInputPropsT,
   overrides: InputComponentsT,
-  label: ?(React.Node | ((props: PropsT) => React.Node)),
-  caption: ?(React.Node | ((props: PropsT) => React.Node)),
   startEnhancer: ?(React.Node | ((props: PropsT) => React.Node)),
   endEnhancer: ?(React.Node | ((props: PropsT) => React.Node)),
   onFocus: (e: SyntheticEventT) => void,
diff --git a/src/select/select.js b/src/select/select.js
index 092e96d39..9fb11abae 100644
--- a/src/select/select.js
+++ b/src/select/select.js
@@ -192,7 +192,7 @@ class Select extends React.Component<PropsT, StatelessStateT> {
     const Root = getOverride(RootOverride) || StyledRoot;
     const Input = getOverride(InputOverride) || StyledInput;
     const SearchIcon = getOverride(SearchIconOverride) || StyledSearchIcon;
-    const {label, placeholder} = this.props;
+    const {placeholder} = this.props;
     const {selectedOptions} = this.state;
     return (
       <div
@@ -202,7 +202,6 @@ class Select extends React.Component<PropsT, StatelessStateT> {
       >
         <InputComponent
           disabled={true}
-          label={label}
           placeholder={!selectedOptions.length ? placeholder : ''}
           overrides={{
             Root: Root,
@@ -234,12 +233,11 @@ class Select extends React.Component<PropsT, StatelessStateT> {
     const InputContainer =
       getOverride(InputContainerOverride) || StyledInputContainer;
     const SearchIcon = getOverride(SearchIconOverride) || StyledSearchIcon;
-    const {label, placeholder, error} = this.props;
+    const {placeholder, error} = this.props;
     const {textValue} = this.state;
     return (
       <InputComponent
-        error={error}
-        label={label}
+        error={!!error}
         placeholder={placeholder}
         value={textValue}
         //$FlowFixMe
diff --git a/src/styles/styled.js b/src/styles/styled.js
index 8c5f61ec6..84d226f3d 100644
--- a/src/styles/styled.js
+++ b/src/styles/styled.js
@@ -27,7 +27,10 @@ const baseStyled = createStyled({wrapper, getInitialStyle, driver});
 export default function styledWrapper(...args: any) {
   // If user is trying to style a styled component
   // use withStyleDeep, otherwise use baseStyled
-  const styleFn = typeof args[0] === 'function' ? withStyleDeep : baseStyled;
+  let styleFn = baseStyled;
+  if (args[0] && args[0].__STYLETRON__) {
+    styleFn = withStyleDeep;
+  }
   // Also allow passing deep style overrides via $style prop
   // Ex: <StyledDiv $style={{color: 'red'}} />
   // Issue for supporting this natively in styletron:


Test patch:
diff --git a/src/form-control/__tests__/__snapshots__/styled-components.test.js.snap b/src/form-control/__tests__/__snapshots__/styled-components.test.js.snap
new file mode 100644
index 000000000..dc683494a
--- /dev/null
+++ b/src/form-control/__tests__/__snapshots__/styled-components.test.js.snap
@@ -0,0 +1,95 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FormControl - Label and Caption for controls StyledCaption - basic render: StyledCaption has correct default styles 1`] = `
+Object {
+  "color": "$theme.colors.mono800",
+  "fontFamily": "$theme.typography.font200.fontFamily",
+  "fontSize": "$theme.typography.font200.fontSize",
+  "fontWeight": "$theme.typography.font200.fontWeight",
+  "lineHeight": "$theme.typography.font200.lineHeight",
+  "marginBottom": "$theme.sizing.scale300",
+  "marginLeft": "0",
+  "marginRight": "0",
+  "marginTop": "$theme.sizing.scale300",
+  "paddingBottom": "0",
+  "paddingLeft": "0",
+  "paddingRight": "0",
+  "paddingTop": "0",
+}
+`;
+
+exports[`FormControl - Label and Caption for controls StyledCaption - basic render: StyledCaption has correct styles when compact and error is boolean 1`] = `
+Object {
+  "color": "$theme.colors.mono800",
+  "fontFamily": "$theme.typography.font200.fontFamily",
+  "fontSize": "$theme.typography.font200.fontSize",
+  "fontWeight": "$theme.typography.font200.fontWeight",
+  "lineHeight": "$theme.typography.font200.lineHeight",
+  "marginBottom": "$theme.sizing.scale300",
+  "marginLeft": "0",
+  "marginRight": "0",
+  "marginTop": "$theme.sizing.scale300",
+  "paddingBottom": "0",
+  "paddingLeft": "0",
+  "paddingRight": "0",
+  "paddingTop": "0",
+}
+`;
+
+exports[`FormControl - Label and Caption for controls StyledCaption - basic render: StyledCaption has correct styles when error is a string 1`] = `
+Object {
+  "color": "$theme.colors.negative400",
+  "fontFamily": "$theme.typography.font200.fontFamily",
+  "fontSize": "$theme.typography.font200.fontSize",
+  "fontWeight": "$theme.typography.font200.fontWeight",
+  "lineHeight": "$theme.typography.font200.lineHeight",
+  "marginBottom": "$theme.sizing.scale300",
+  "marginLeft": "0",
+  "marginRight": "0",
+  "marginTop": "$theme.sizing.scale300",
+  "paddingBottom": "0",
+  "paddingLeft": "0",
+  "paddingRight": "0",
+  "paddingTop": "0",
+}
+`;
+
+exports[`FormControl - Label and Caption for controls StyledLabel - basic render: StyledLabel has correct default styles 1`] = `
+Object {
+  "color": "$theme.colors.mono1000",
+  "cursor": "auto",
+  "display": "block",
+  "fontFamily": "$theme.typography.font350.fontFamily",
+  "fontSize": "$theme.typography.font350.fontSize",
+  "fontWeight": 500,
+  "lineHeight": "$theme.typography.font350.lineHeight",
+  "marginBottom": "$theme.sizing.scale300",
+  "marginLeft": "0",
+  "marginRight": "0",
+  "marginTop": "$theme.sizing.scale300",
+  "paddingBottom": "0",
+  "paddingLeft": "0",
+  "paddingRight": "0",
+  "paddingTop": "0",
+}
+`;
+
+exports[`FormControl - Label and Caption for controls StyledLabel - basic render: StyledLabel has correct styles when compact and disabled 1`] = `
+Object {
+  "color": "$theme.colors.mono700",
+  "cursor": "not-allowed",
+  "display": "block",
+  "fontFamily": "$theme.typography.font350.fontFamily",
+  "fontSize": "$theme.typography.font350.fontSize",
+  "fontWeight": 500,
+  "lineHeight": "$theme.typography.font350.lineHeight",
+  "marginBottom": "$theme.sizing.scale300",
+  "marginLeft": "0",
+  "marginRight": "0",
+  "marginTop": "$theme.sizing.scale300",
+  "paddingBottom": "0",
+  "paddingLeft": "0",
+  "paddingRight": "0",
+  "paddingTop": "0",
+}
+`;
diff --git a/src/form-control/__tests__/form-control.test.js b/src/form-control/__tests__/form-control.test.js
new file mode 100644
index 000000000..5695393ec
--- /dev/null
+++ b/src/form-control/__tests__/form-control.test.js
@@ -0,0 +1,247 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+import React from 'react';
+import {mount} from 'enzyme';
+import FormControl from '../form-control';
+import {Label, Caption} from '../styled-components';
+import {Input} from '../../input';
+import {Textarea} from '../../textarea';
+import {Checkbox} from '../../checkbox';
+import {RadioGroup, StyledRadio} from '../../radio';
+
+describe('FormControl - Label and Caption for controls', () => {
+  test('Renders label, caption, and error for the Input component', () => {
+    const rendered = mount(
+      <FormControl label="Label test" caption="Caption test">
+        <Input />
+      </FormControl>,
+    );
+    const label = rendered.find(Label).first();
+    expect(label).toExist();
+    expect(label).toHaveText('Label test');
+    expect(label.props()).toMatchObject({
+      $disabled: false,
+      $error: false,
+      $required: false,
+      $size: 'default',
+      children: 'Label test',
+    });
+    let caption = rendered.find(Caption).first();
+    expect(caption).toExist();
+    expect(caption).toHaveText('Caption test');
+    expect(caption.props()).toMatchObject({
+      $disabled: false,
+      $error: false,
+      $required: false,
+      $size: 'default',
+      children: 'Caption test',
+    });
+
+    rendered.setProps({
+      error: 'Error test',
+    });
+    caption = rendered.find(Caption).first();
+    expect(caption).toHaveText('Error test');
+    expect(caption.props()).toMatchObject({
+      $disabled: false,
+      $error: 'Error test',
+      $required: false,
+      $size: 'default',
+      children: 'Error test',
+    });
+  });
+
+  test('Accepts a node for label, caption, and error', () => {
+    const rendered = mount(
+      <FormControl
+        label={<span>Label test</span>}
+        caption={<span>Caption test</span>}
+      >
+        <Input />
+      </FormControl>,
+    );
+    const label = rendered.find(Label).first();
+    expect(label).toHaveText('Label test');
+    const caption = rendered.find(Caption).first();
+    expect(caption).toHaveText('Caption test');
+
+    rendered.setProps({
+      error: <span>Error test</span>,
+    });
+    expect(caption).toHaveText('Error test');
+  });
+
+  test('Accepts a function for label, caption, and error', () => {
+    const label = jest.fn().mockReturnValue(<span>Label test</span>);
+    const caption = jest.fn().mockReturnValue(<span>Caption test</span>);
+    const error = jest.fn().mockReturnValue(<span>Error test</span>);
+    const rendered = mount(
+      <FormControl label={label} caption={caption}>
+        <Input />
+      </FormControl>,
+    );
+    const sharedProps = {
+      $disabled: false,
+      $error: false,
+      $required: false,
+      $size: 'default',
+    };
+    const labelRendered = rendered.find(Label).first();
+    expect(label).toHaveBeenCalledWith(sharedProps);
+    expect(labelRendered).toHaveText('Label test');
+    const captionRendered = rendered.find(Caption).first();
+    expect(caption).toHaveBeenCalledWith(sharedProps);
+    expect(captionRendered).toHaveText('Caption test');
+    expect(error).not.toHaveBeenCalled();
+
+    rendered.setProps({
+      error: error,
+    });
+    expect(error).toHaveBeenCalledWith({
+      ...sharedProps,
+      $error: error,
+    });
+    expect(captionRendered).toHaveText('Error test');
+  });
+
+  test('Renders caption if error is set on the control', () => {
+    const rendered = mount(
+      <FormControl label="Label test" caption="Caption test">
+        <Input error />
+      </FormControl>,
+    );
+    const caption = rendered.find(Caption).first();
+    expect(caption).toHaveText('Caption test');
+    expect(caption.props()).toMatchObject({
+      $disabled: false,
+      $error: true,
+      $required: false,
+      $size: 'default',
+      children: 'Caption test',
+    });
+  });
+
+  test('Passes correct props from the control to label and caption', () => {
+    const rendered = mount(
+      <FormControl label="Label test" caption="Caption test">
+        <Input disabled required error />
+      </FormControl>,
+    );
+    const label = rendered.find(Label).first();
+    expect(label.props()).toMatchObject({
+      $disabled: true,
+      $error: true,
+      $required: true,
+      $size: 'default',
+      children: 'Label test',
+    });
+    const caption = rendered.find(Caption).first();
+    expect(caption.props()).toMatchObject({
+      $disabled: true,
+      $error: true,
+      $required: true,
+      $size: 'default',
+      children: 'Caption test',
+    });
+    rendered.setProps({
+      error: 'Error test',
+    });
+    expect(label.props()).toMatchObject({
+      $disabled: true,
+      $error: true,
+      $required: true,
+      $size: 'default',
+      children: 'Label test',
+    });
+    expect(caption.props()).toMatchObject({
+      $disabled: true,
+      $error: true,
+      $required: true,
+      $size: 'default',
+      children: 'Caption test',
+    });
+    expect(caption).toHaveText('Error test');
+  });
+
+  test('Renders label and caption for the Teaxtarea component', () => {
+    const rendered = mount(
+      <FormControl label="Label test" caption="Caption test">
+        <Textarea required />
+      </FormControl>,
+    );
+    const label = rendered.find(Label).first();
+    expect(label).toExist();
+    expect(label.props()).toMatchObject({
+      $disabled: false,
+      $error: false,
+      $required: true,
+      $size: 'default',
+      children: 'Label test',
+    });
+    const caption = rendered.find(Caption).first();
+    expect(caption).toExist();
+    expect(caption.props()).toMatchObject({
+      $disabled: false,
+      $error: false,
+      $required: true,
+      $size: 'default',
+      children: 'Caption test',
+    });
+  });
+});
+
+test('Renders label and caption for the Checkbox component', () => {
+  const rendered = mount(
+    <FormControl label="Label test" caption="Caption test">
+      <Checkbox required />
+    </FormControl>,
+  );
+  const label = rendered.find(Label).first();
+  expect(label).toExist();
+  expect(label.props()).toMatchObject({
+    $disabled: false,
+    $error: undefined,
+    $required: true,
+    children: 'Label test',
+  });
+  const caption = rendered.find(Caption).first();
+  expect(caption).toExist();
+  expect(caption.props()).toMatchObject({
+    $disabled: false,
+    $error: undefined,
+    $required: true,
+    children: 'Caption test',
+  });
+});
+
+test('Renders label and caption for the RadioGroup component', () => {
+  const rendered = mount(
+    <FormControl label="Label test" caption="Caption test">
+      <RadioGroup required>
+        <StyledRadio value="1">First</StyledRadio>
+        <StyledRadio value="2">Second</StyledRadio>
+        <StyledRadio value="3">Third</StyledRadio>
+      </RadioGroup>
+    </FormControl>,
+  );
+  const label = rendered.find(Label).first();
+  expect(label).toExist();
+  expect(label.props()).toMatchObject({
+    $disabled: false,
+    $error: undefined,
+    $required: true,
+    children: 'Label test',
+  });
+  const caption = rendered.find(Caption).first();
+  expect(caption).toExist();
+  expect(caption.props()).toMatchObject({
+    $disabled: false,
+    $error: undefined,
+    $required: true,
+    children: 'Caption test',
+  });
+});
diff --git a/src/form-control/__tests__/styled-components.test.js b/src/form-control/__tests__/styled-components.test.js
new file mode 100644
index 000000000..5354e2588
--- /dev/null
+++ b/src/form-control/__tests__/styled-components.test.js
@@ -0,0 +1,46 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+import React from 'react';
+import {shallow} from 'enzyme';
+import {
+  Label as StyledLabel,
+  Caption as StyledCaption,
+} from '../styled-components';
+
+describe('FormControl - Label and Caption for controls', () => {
+  test('StyledLabel - basic render', () => {
+    const component = shallow(<StyledLabel>Label</StyledLabel>);
+    expect(component.instance().getStyles()).toMatchSnapshot(
+      'StyledLabel has correct default styles',
+    );
+    component.setProps({
+      $disabled: true,
+    });
+    expect(component.instance().getStyles()).toMatchSnapshot(
+      'StyledLabel has correct styles when compact and disabled',
+    );
+  });
+
+  test('StyledCaption - basic render', () => {
+    const component = shallow(<StyledCaption>Caption</StyledCaption>);
+    expect(component.instance().getStyles()).toMatchSnapshot(
+      'StyledCaption has correct default styles',
+    );
+    component.setProps({
+      $error: true,
+    });
+    expect(component.instance().getStyles()).toMatchSnapshot(
+      'StyledCaption has correct styles when compact and error is boolean',
+    );
+    component.setProps({
+      $error: 'Error message',
+    });
+    expect(component.instance().getStyles()).toMatchSnapshot(
+      'StyledCaption has correct styles when error is a string',
+    );
+  });
+});
diff --git a/src/input/__tests__/__snapshots__/base-input.test.js.snap b/src/input/__tests__/__snapshots__/base-input.test.js.snap
index 63fcf1a34..93cb06659 100644
--- a/src/input/__tests__/__snapshots__/base-input.test.js.snap
+++ b/src/input/__tests__/__snapshots__/base-input.test.js.snap
@@ -29,6 +29,7 @@ Object {
   "children": null,
   "disabled": false,
   "id": "bui-mock-id",
+  "name": "",
   "onBlur": [Function],
   "onChange": [MockFunction],
   "onFocus": [Function],
diff --git a/src/input/__tests__/__snapshots__/input.test.js.snap b/src/input/__tests__/__snapshots__/input.test.js.snap
index 097122f9a..be7ee7028 100644
--- a/src/input/__tests__/__snapshots__/input.test.js.snap
+++ b/src/input/__tests__/__snapshots__/input.test.js.snap
@@ -7,6 +7,7 @@ Object {
   "children": null,
   "disabled": false,
   "id": "bui-mock-id",
+  "name": "",
   "onBlur": [Function],
   "onChange": [MockFunction],
   "onFocus": [Function],
@@ -43,6 +44,7 @@ Object {
       aria-required={false}
       disabled={false}
       id="bui-mock-id"
+      name=""
       onBlur={[Function]}
       onChange={[MockFunction]}
       onFocus={[Function]}
@@ -81,6 +83,7 @@ Object {
       aria-required={false}
       disabled={false}
       id="bui-mock-id"
+      name=""
       onBlur={[Function]}
       onChange={[MockFunction]}
       onFocus={[Function]}
@@ -119,6 +122,7 @@ Object {
       aria-required={false}
       disabled={false}
       id="bui-mock-id"
+      name=""
       onBlur={[Function]}
       onChange={[MockFunction]}
       onFocus={[Function]}
diff --git a/src/input/__tests__/__snapshots__/stateful-container.test.js.snap b/src/input/__tests__/__snapshots__/stateful-container.test.js.snap
index db97399a2..d4f599daf 100644
--- a/src/input/__tests__/__snapshots__/stateful-container.test.js.snap
+++ b/src/input/__tests__/__snapshots__/stateful-container.test.js.snap
@@ -3,9 +3,6 @@
 exports[`StatefulContainer - basic render: function-as-child called with correct args 1`] = `
 Array [
   Object {
-    "components": Object {
-      "Label": [Function],
-    },
     "onChange": [Function],
     "value": "initial value",
   },
diff --git a/src/input/__tests__/__snapshots__/stateful-input.test.js.snap b/src/input/__tests__/__snapshots__/stateful-input.test.js.snap
index 778440e1f..909f60e43 100644
--- a/src/input/__tests__/__snapshots__/stateful-input.test.js.snap
+++ b/src/input/__tests__/__snapshots__/stateful-input.test.js.snap
@@ -3,12 +3,11 @@
 exports[`StatefulInput - basic render: renders <Input/> as a child 1`] = `
 <Input
   autoFocus={false}
-  caption="Caption"
   disabled={false}
   endEnhancer={null}
   error={false}
   id="bui-mock-id"
-  label="Label"
+  name=""
   onBlur={[Function]}
   onChange={[Function]}
   onFocus={[Function]}
@@ -26,9 +25,7 @@ exports[`StatefulInput - basic render: renders <Input/> as a child 1`] = `
 
 exports[`StatefulInput - basic render: renders <StatefulContainer/> 1`] = `
 <StatefulContainer
-  caption="Caption"
   initialState={Object {}}
-  label="Label"
   onChange={[MockFunction]}
   overrides={
     Object {
diff --git a/src/input/__tests__/__snapshots__/styled-components.test.js.snap b/src/input/__tests__/__snapshots__/styled-components.test.js.snap
index 01fa2d1b6..7935e68f6 100644
--- a/src/input/__tests__/__snapshots__/styled-components.test.js.snap
+++ b/src/input/__tests__/__snapshots__/styled-components.test.js.snap
@@ -1,59 +1,5 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`Input - StyledCaption - basic render: StyledCaption has correct default styles 1`] = `
-Object {
-  "color": "$theme.colors.mono800",
-  "fontFamily": "$theme.typography.font200.fontFamily",
-  "fontSize": "$theme.typography.font200.fontSize",
-  "fontWeight": "$theme.typography.font200.fontWeight",
-  "lineHeight": "$theme.typography.font200.lineHeight",
-  "marginBottom": "$theme.sizing.scale300",
-  "marginLeft": "0",
-  "marginRight": "0",
-  "marginTop": "$theme.sizing.scale300",
-  "paddingBottom": "0",
-  "paddingLeft": "0",
-  "paddingRight": "0",
-  "paddingTop": "0",
-}
-`;
-
-exports[`Input - StyledCaption - basic render: StyledCaption has correct styles when compact and error is boolean 1`] = `
-Object {
-  "color": "$theme.colors.mono800",
-  "fontFamily": "$theme.typography.font200.fontFamily",
-  "fontSize": "$theme.typography.font200.fontSize",
-  "fontWeight": "$theme.typography.font200.fontWeight",
-  "lineHeight": "$theme.typography.font200.lineHeight",
-  "marginBottom": "$theme.sizing.scale300",
-  "marginLeft": "0",
-  "marginRight": "0",
-  "marginTop": "$theme.sizing.scale300",
-  "paddingBottom": "0",
-  "paddingLeft": "0",
-  "paddingRight": "0",
-  "paddingTop": "0",
-}
-`;
-
-exports[`Input - StyledCaption - basic render: StyledCaption has correct styles when error is a node 1`] = `
-Object {
-  "color": "$theme.colors.negative400",
-  "fontFamily": "$theme.typography.font200.fontFamily",
-  "fontSize": "$theme.typography.font200.fontSize",
-  "fontWeight": "$theme.typography.font200.fontWeight",
-  "lineHeight": "$theme.typography.font200.lineHeight",
-  "marginBottom": "$theme.sizing.scale300",
-  "marginLeft": "0",
-  "marginRight": "0",
-  "marginTop": "$theme.sizing.scale300",
-  "paddingBottom": "0",
-  "paddingLeft": "0",
-  "paddingRight": "0",
-  "paddingTop": "0",
-}
-`;
-
 exports[`Input - StyledInput - basic render: StyledInput has correct default styles 1`] = `
 Object {
   "::placeholder": Object {
@@ -320,44 +266,6 @@ Object {
 }
 `;
 
-exports[`Input - StyledLabel - basic render: StyledLabel has correct default styles 1`] = `
-Object {
-  "color": "$theme.colors.mono1000",
-  "display": "block",
-  "fontFamily": "$theme.typography.font350.fontFamily",
-  "fontSize": "$theme.typography.font350.fontSize",
-  "fontWeight": 500,
-  "lineHeight": "$theme.typography.font350.lineHeight",
-  "marginBottom": "$theme.sizing.scale300",
-  "marginLeft": "0",
-  "marginRight": "0",
-  "marginTop": "$theme.sizing.scale300",
-  "paddingBottom": "0",
-  "paddingLeft": "0",
-  "paddingRight": "0",
-  "paddingTop": "0",
-}
-`;
-
-exports[`Input - StyledLabel - basic render: StyledLabel has correct styles when compact and disabled 1`] = `
-Object {
-  "color": "$theme.colors.mono700",
-  "display": "block",
-  "fontFamily": "$theme.typography.font350.fontFamily",
-  "fontSize": "$theme.typography.font350.fontSize",
-  "fontWeight": 500,
-  "lineHeight": "$theme.typography.font350.lineHeight",
-  "marginBottom": "$theme.sizing.scale300",
-  "marginLeft": "0",
-  "marginRight": "0",
-  "marginTop": "$theme.sizing.scale300",
-  "paddingBottom": "0",
-  "paddingLeft": "0",
-  "paddingRight": "0",
-  "paddingTop": "0",
-}
-`;
-
 exports[`Input - StyledRoot - basic render: StyledRoot has correct default styles 1`] = `
 Object {
   "color": "$theme.colors.mono1000",
diff --git a/src/input/__tests__/input.test.js b/src/input/__tests__/input.test.js
index e331b765a..2a2ea9720 100644
--- a/src/input/__tests__/input.test.js
+++ b/src/input/__tests__/input.test.js
@@ -12,8 +12,6 @@ import {Input, StyledInputContainer} from '../index';
 test('Input - basic functionality', () => {
   const props = {
     value: 'input value',
-    label: 'This is label',
-    caption: 'This is caption',
     placeholder: 'Placeholder',
     onFocus: jest.fn(),
     onBlur: jest.fn(),
@@ -23,19 +21,11 @@ test('Input - basic functionality', () => {
   const wrapper = mount(<Input {...props} />);
   expect(wrapper).toHaveState('isFocused', false);
 
-  // Renders input, label and caption
+  // Renders input
   const renderedInput = wrapper.find('input').first();
   expect(renderedInput).toExist();
   expect(renderedInput.props()).toMatchSnapshot('input has correct props');
 
-  const renderedLabel = wrapper.find('label').first();
-  expect(renderedLabel).toExist();
-  expect(renderedLabel).toHaveText('This is label');
-
-  const renderedCaption = wrapper.find('div').last();
-  expect(renderedCaption).toExist();
-  expect(renderedCaption).toHaveText('This is caption');
-
   // onFocus handler from props is called
   renderedInput.simulate('focus');
   expect(props.onFocus).toBeCalled();
@@ -61,7 +51,7 @@ test('Input - renders enhancers', () => {
   const wrapper = mount(<Input {...props} />);
   expect(wrapper).toHaveState('isFocused', false);
 
-  // Renders input, label and caption
+  // Renders input and exhancers
   const renderedInput = wrapper.find(StyledInputContainer).first();
   expect(renderedInput).toExist();
   expect(renderedInput.props()).toMatchSnapshot(
diff --git a/src/input/__tests__/stateful-container.test.js b/src/input/__tests__/stateful-container.test.js
index 0a7e34f71..373396c2e 100644
--- a/src/input/__tests__/stateful-container.test.js
+++ b/src/input/__tests__/stateful-container.test.js
@@ -12,11 +12,6 @@ import {StatefulContainer, STATE_CHANGE_TYPE} from '../index';
 
 test('StatefulContainer - basic render', () => {
   const props = {
-    components: {
-      Label: function CustomLabel() {
-        return <span />;
-      },
-    },
     initialState: {value: 'initial value'},
     onChange: jest.fn(),
     stateReducer: jest.fn(),
diff --git a/src/input/__tests__/stateful-input.test.js b/src/input/__tests__/stateful-input.test.js
index 352c1824c..3ee5f26d7 100644
--- a/src/input/__tests__/stateful-input.test.js
+++ b/src/input/__tests__/stateful-input.test.js
@@ -12,8 +12,6 @@ import {StatefulInput, StyledInput} from '../index';
 test('StatefulInput - basic render', () => {
   const props = {
     onChange: jest.fn(),
-    label: 'Label',
-    caption: 'Caption',
     overrides: {
       Input: function CustomInput(props) {
         return (
diff --git a/src/input/__tests__/styled-components.test.js b/src/input/__tests__/styled-components.test.js
index 0cd1dfae3..cd8519f98 100644
--- a/src/input/__tests__/styled-components.test.js
+++ b/src/input/__tests__/styled-components.test.js
@@ -8,35 +8,15 @@ LICENSE file in the root directory of this source tree.
 import React from 'react';
 import {shallow} from 'enzyme';
 import {
-  StyledLabel,
   StyledRoot,
   StyledInputEnhancer,
   StyledInputContainer,
   StyledInput,
-  StyledCaption,
   SIZE,
   ADJOINED,
 } from '../index';
 import {ENHANCER_POSITION} from '../constants';
 
-test('Input - StyledLabel - basic render', () => {
-  const component = shallow(
-    <StyledLabel $size={SIZE.default}>
-      <span />
-    </StyledLabel>,
-  );
-  expect(component.instance().getStyles()).toMatchSnapshot(
-    'StyledLabel has correct default styles',
-  );
-  component.setProps({
-    $size: SIZE.compact,
-    $disabled: true,
-  });
-  expect(component.instance().getStyles()).toMatchSnapshot(
-    'StyledLabel has correct styles when compact and disabled',
-  );
-});
-
 test('Input - StyledRoot - basic render', () => {
   const component = shallow(
     <StyledRoot $size={SIZE.default}>
@@ -141,27 +121,3 @@ test('Input - StyledInput - basic render', () => {
     'StyledInput has correct styles when compact and disabled',
   );
 });
-
-test('Input - StyledCaption - basic render', () => {
-  const component = shallow(
-    <StyledCaption $size={SIZE.default}>
-      <span />
-    </StyledCaption>,
-  );
-  expect(component.instance().getStyles()).toMatchSnapshot(
-    'StyledCaption has correct default styles',
-  );
-  component.setProps({
-    $size: SIZE.compact,
-    $error: true,
-  });
-  expect(component.instance().getStyles()).toMatchSnapshot(
-    'StyledCaption has correct styles when compact and error is boolean',
-  );
-  component.setProps({
-    $error: <span>Error message</span>,
-  });
-  expect(component.instance().getStyles()).toMatchSnapshot(
-    'StyledCaption has correct styles when error is a node',
-  );
-});
diff --git a/src/select/__tests__/__snapshots__/select.test.js.snap b/src/select/__tests__/__snapshots__/select.test.js.snap
index fe714f1cd..fcfcff5dc 100644
--- a/src/select/__tests__/__snapshots__/select.test.js.snap
+++ b/src/select/__tests__/__snapshots__/select.test.js.snap
@@ -25,12 +25,11 @@ exports[`Stateless select should render component in search mode and false for m
     >
       <Input
         autoFocus={false}
-        caption={null}
         disabled={false}
         endEnhancer={null}
-        error=""
+        error={false}
         id="bui-mock-id"
-        label={null}
+        name=""
         onBlur={[Function]}
         onChange={[Function]}
         onFocus={[Function]}
@@ -53,7 +52,7 @@ exports[`Stateless select should render component in search mode and false for m
       >
         <MockStyledComponent
           $disabled={false}
-          $error=""
+          $error={false}
           $isFocused={false}
           $required={false}
           $size="default"
@@ -65,13 +64,14 @@ exports[`Stateless select should render component in search mode and false for m
               adjoined="none"
               autoFocus={false}
               disabled={false}
-              error=""
+              error={false}
               id="bui-mock-id"
               inputRef={
                 Object {
                   "current": null,
                 }
               }
+              name=""
               onBlur={[Function]}
               onChange={[Function]}
               onFocus={[Function]}
@@ -97,7 +97,7 @@ exports[`Stateless select should render component in search mode and false for m
               <MockStyledComponent
                 $adjoined="none"
                 $disabled={false}
-                $error=""
+                $error={false}
                 $isFocused={false}
                 $required={false}
                 $size="default"
@@ -108,7 +108,7 @@ exports[`Stateless select should render component in search mode and false for m
                   <Before
                     $adjoined="none"
                     $disabled={false}
-                    $error=""
+                    $error={false}
                     $isFocused={false}
                     $required={false}
                     $size="default"
@@ -126,7 +126,7 @@ exports[`Stateless select should render component in search mode and false for m
                   <MockStyledComponent
                     $adjoined="none"
                     $disabled={false}
-                    $error=""
+                    $error={false}
                     $isFocused={false}
                     $ref={
                       Object {
@@ -139,6 +139,7 @@ exports[`Stateless select should render component in search mode and false for m
                     aria-required={false}
                     disabled={false}
                     id="bui-mock-id"
+                    name=""
                     onBlur={[Function]}
                     onChange={[Function]}
                     onFocus={[Function]}
@@ -152,6 +153,7 @@ exports[`Stateless select should render component in search mode and false for m
                       aria-required={false}
                       disabled={false}
                       id="bui-mock-id"
+                      name=""
                       onBlur={[Function]}
                       onChange={[Function]}
                       onFocus={[Function]}
@@ -165,7 +167,7 @@ exports[`Stateless select should render component in search mode and false for m
                   <After
                     $adjoined="none"
                     $disabled={false}
-                    $error=""
+                    $error={false}
                     $isFocused={false}
                     $required={false}
                     $size="default"
@@ -229,12 +231,11 @@ exports[`Stateless select should render component in search mode and true for mu
     >
       <Input
         autoFocus={false}
-        caption={null}
         disabled={false}
         endEnhancer={null}
-        error=""
+        error={false}
         id="bui-mock-id"
-        label={null}
+        name=""
         onBlur={[Function]}
         onChange={[Function]}
         onFocus={[Function]}
@@ -257,7 +258,7 @@ exports[`Stateless select should render component in search mode and true for mu
       >
         <MockStyledComponent
           $disabled={false}
-          $error=""
+          $error={false}
           $isFocused={false}
           $required={false}
           $size="default"
@@ -269,13 +270,14 @@ exports[`Stateless select should render component in search mode and true for mu
               adjoined="none"
               autoFocus={false}
               disabled={false}
-              error=""
+              error={false}
               id="bui-mock-id"
               inputRef={
                 Object {
                   "current": null,
                 }
               }
+              name=""
               onBlur={[Function]}
               onChange={[Function]}
               onFocus={[Function]}
@@ -301,7 +303,7 @@ exports[`Stateless select should render component in search mode and true for mu
               <MockStyledComponent
                 $adjoined="none"
                 $disabled={false}
-                $error=""
+                $error={false}
                 $isFocused={false}
                 $required={false}
                 $size="default"
@@ -312,7 +314,7 @@ exports[`Stateless select should render component in search mode and true for mu
                   <Before
                     $adjoined="none"
                     $disabled={false}
-                    $error=""
+                    $error={false}
                     $isFocused={false}
                     $required={false}
                     $size="default"
@@ -330,7 +332,7 @@ exports[`Stateless select should render component in search mode and true for mu
                   <MockStyledComponent
                     $adjoined="none"
                     $disabled={false}
-                    $error=""
+                    $error={false}
                     $isFocused={false}
                     $ref={
                       Object {
@@ -343,6 +345,7 @@ exports[`Stateless select should render component in search mode and true for mu
                     aria-required={false}
                     disabled={false}
                     id="bui-mock-id"
+                    name=""
                     onBlur={[Function]}
                     onChange={[Function]}
                     onFocus={[Function]}
@@ -356,6 +359,7 @@ exports[`Stateless select should render component in search mode and true for mu
                       aria-required={false}
                       disabled={false}
                       id="bui-mock-id"
+                      name=""
                       onBlur={[Function]}
                       onChange={[Function]}
                       onFocus={[Function]}
@@ -369,7 +373,7 @@ exports[`Stateless select should render component in search mode and true for mu
                   <After
                     $adjoined="none"
                     $disabled={false}
-                    $error=""
+                    $error={false}
                     $isFocused={false}
                     $required={false}
                     $size="default"
@@ -436,12 +440,11 @@ exports[`Stateless select should render component in select mode and false for m
       >
         <Input
           autoFocus={false}
-          caption={null}
           disabled={true}
           endEnhancer={null}
           error={false}
           id="bui-mock-id"
-          label={null}
+          name=""
           onBlur={[Function]}
           onFocus={[Function]}
           overrides={
@@ -477,6 +480,7 @@ exports[`Stateless select should render component in select mode and false for m
                     "current": null,
                   }
                 }
+                name=""
                 onBlur={[Function]}
                 onChange={[Function]}
                 onFocus={[Function]}
@@ -529,6 +533,7 @@ exports[`Stateless select should render component in select mode and false for m
                       aria-required={false}
                       disabled={true}
                       id="bui-mock-id"
+                      name=""
                       onBlur={[Function]}
                       onChange={[Function]}
                       onFocus={[Function]}
@@ -541,6 +546,7 @@ exports[`Stateless select should render component in select mode and false for m
                         aria-required={false}
                         disabled={true}
                         id="bui-mock-id"
+                        name=""
                         onBlur={[Function]}
                         onChange={[Function]}
                         onFocus={[Function]}
@@ -619,12 +625,11 @@ exports[`Stateless select should render component in select mode and true for mu
       >
         <Input
           autoFocus={false}
-          caption={null}
           disabled={true}
           endEnhancer={null}
           error={false}
           id="bui-mock-id"
-          label={null}
+          name=""
           onBlur={[Function]}
           onFocus={[Function]}
           overrides={
@@ -660,6 +665,7 @@ exports[`Stateless select should render component in select mode and true for mu
                     "current": null,
                   }
                 }
+                name=""
                 onBlur={[Function]}
                 onChange={[Function]}
                 onFocus={[Function]}
@@ -712,6 +718,7 @@ exports[`Stateless select should render component in select mode and true for mu
                       aria-required={false}
                       disabled={true}
                       id="bui-mock-id"
+                      name=""
                       onBlur={[Function]}
                       onChange={[Function]}
                       onFocus={[Function]}
@@ -724,6 +731,7 @@ exports[`Stateless select should render component in select mode and true for mu
                         aria-required={false}
                         disabled={true}
                         id="bui-mock-id"
+                        name=""
                         onBlur={[Function]}
                         onChange={[Function]}
                         onFocus={[Function]}
diff --git a/src/styles/__tests__/styled.test.js b/src/styles/__tests__/styled.test.js
index a45e67a3d..be4d2a876 100644
--- a/src/styles/__tests__/styled.test.js
+++ b/src/styles/__tests__/styled.test.js
@@ -90,10 +90,13 @@ test('styled override styled component', () => {
   const StyledBaseOverride = styled(StyledBase, {
     color: 'blue',
   });
+  const BaseComp = props => <div {...props} />;
+  const StyledBaseComp = styled(BaseComp, {color: 'black'});
   const TestComponent = withStyletronProvider(() => (
     <div>
       <StyledBase />
       <StyledBaseOverride />
+      <StyledBaseComp />
     </div>
   ));
 
@@ -108,5 +111,9 @@ test('styled override styled component', () => {
   expect(override.classList).toHaveLength(1);
   expect(override.classList).not.toContain(redColorClass);
 
+  // Making sure this doesn't throw an error
+  const styledBase = wrapper.find(StyledBaseComp).getDOMNode();
+  expect(styledBase.classList).toHaveLength(1);
+
   wrapper.unmount();
 });
diff --git a/src/textarea/__tests__/__snapshots__/textarea.test.js.snap b/src/textarea/__tests__/__snapshots__/textarea.test.js.snap
index 6007b2b50..476942ab1 100644
--- a/src/textarea/__tests__/__snapshots__/textarea.test.js.snap
+++ b/src/textarea/__tests__/__snapshots__/textarea.test.js.snap
@@ -7,6 +7,7 @@ Object {
   "children": "textarea value",
   "disabled": false,
   "id": "bui-mock-id",
+  "name": "",
   "onBlur": [Function],
   "onChange": [MockFunction],
   "onFocus": [Function],