Repository: uber-web/baseui
Pull request #203 (2018-09-06 18:41:49)

docs(toast): toast notification rfc
![screen shot 2018-09-06 at 11 43 50 am](https://user-images.githubusercontent.com/1920399/45178333-34ac0900-b1ca-11e8-848d-1c3c24629102.png)



Patch:
diff --git a/package.json b/package.json
index 2b094daff..dc8046b02 100644
--- a/package.json
+++ b/package.json
@@ -5,14 +5,15 @@
   "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/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/toast-component.md b/rfcs/toast-component.md
new file mode 100644
index 000000000..0a4182128
--- /dev/null
+++ b/rfcs/toast-component.md
@@ -0,0 +1,117 @@
+# Toast Notification Component
+
+Toast notifications are used to display a brief alert, warning, or confirmation message or a banner.
+
+## Usage
+
+### Basic usage
+
+```javascript
+import * as React from 'react';
+import {Toast} from 'baseui/toast';
+
+export default () => <Toast>This is a notification message.</Toast>;
+```
+
+### Advanced usage
+
+```javascript
+import * as React from 'react';
+import {Toast} from 'baseui/toast';
+
+export default () => {
+  return (
+    <React.Fragment>
+      <Toast
+        onClose={()=>{console.log('Toast notification was dismissed.')}}
+        autoHideDuration={5000}
+        overrides={{
+          Body: props => <CustomContainer>{props.children}</CustomContainer>,
+        }}
+      >
+      This a dismissable info notification that is hidden after 5 seconds automatically.
+      </Toast>
+      <Toast
+        closeable={false}
+        kind={KIND.error}
+      >
+      {({dismiss}) => {
+        return (
+          <div>
+            This is an error notification with an action button
+            <button onClick={() => {
+              dismiss();
+              console.log('Action button clicked');
+            }}>
+              Take action
+            </button>
+          </div>
+        );
+      }}
+      </Toast>
+    </React.Fragment>
+  );
+}
+```
+
+## Exports
+
+* `Toast`
+* `StyledRoot`
+* `StyledBody`
+* `StyledCloseButton`
+* `KIND`
+* `PLACEMENT`
+
+## `Toast` API
+
+* `children: React.node | ({dismiss}) => React.node` - Required
+  Toast notification content. The children-as-function receives a `dissmiss` method that can be called to dismiss the notification and can be used as a handler for an action inside the toast content
+* `closeable: boolean` - Optional. Defaults to true
+  When set to true a close button is displayed and the notification can be dismissed by a user
+* `kind: 'info' | 'success' | 'warning' | 'error'` - Optional. Defaults to 'info'
+  Defines the type of notification
+* `onClose: function` - Optional
+  A callback function called when a notification is dismissed
+* `overrides: {Root, Body, CloseButton}` - Optional
+  Overrides for presentational components. See "Presentational Components Props API" below.
+  * `[ComponentName]: ReactComponent | {props: {}, style: {}, component: ReactComponent}` - Optional
+* `placement: 'inline' | 'topLeft' | 'top' | 'topRight' | 'bottomRight' | 'bottom' | 'bottomLeft'` - Optional. Defaults to 'top'
+  Position of a toast notification relative to the browser window. If set to 'inline' a notitication is positioned according to a normal flow of the document
+* `autoHideDuration: number` - Optional. Defaults to 0
+  The number of milliseconds to wait before automatically dismissing a notification. This behavior is disabled when the value is set to 0
+
+## Presentational components props API
+
+These properties are passed to every presentational (styled) component that is exported:
+
+* `$kind: type`
+* `$theme: theme`
+
+## KIND Constant
+
+* `info` - Generally used for messages with an informational context
+* `success` - Generally used as a confirmation of a succesful action or operation  
+* `warning` - Generally used for messages with an warning context
+* `error` - Generally used as a notification of an error happened as a result of an action or operation
+
+## PLACEMENT Constant
+
+* `inline` - positioned according to a normal flow of the document
+* `topLeft` - positioned relative to the browser window
+* `top` - positioned relative to the browser window
+* `topRight` - positioned relative to the browser window
+* `bottomRight` - positioned relative to the browser window
+* `bottom` - positioned relative to the browser window
+* `bottomLeft` - positioned relative to the browser window
+
+## Dependencies
+
+Does this component depend on any 3rd party packages or other internal components?
+
+## Accessibility
+
+* Toast notification container element will have `role="alert"` set on it
+* When multiple alerts are displayed and positioned relative to the browser window they won't overlap but displayed in a column way, latest notifications are displayed at the initial position of a chosen placement
+* When a notification is set to be dissmissed automatically after a provided `autoHideDuration` time hovering or focusing the notification will prevend the notification from disappearing and reset the timeout to the initial `autoHideDuration` value
+* Think about handling a case where the number of notifications is more than a screen can fit, possible solution is to have the notifications that overflow hidden (older notifications) or have the column of notifications scrollable
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


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/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],