Repository: mapbox/mr-ui
Pull request #32 (2018-08-08 21:12:57)

Popover changes
There are a lot of changes in this PR that are mostly just reformatting comments or improving documentation and, especially, documentation examples. But there are also some very important changes, which I hope are improvements, to Popover, PopoverTrigger, and Tooltip.

- There are 3 standard popover styles: light, dark, and warning. 
- `theme*` props have been replaced with higher-level styling props like `coloring` (light, dark, or warning) and `padded` (true or false).
- A new `passthroughProps` convention is introduced to pass props through a component to some child (most often to pass it through directly to a DOM element).
- Children of Tooltip no longer need to be functions!

davidtheclark: @jseppi could you give this a review? There's a lot I think you could skip over, but if the changes and documentation make sense to you, and the examples (in docs and test cases) are clear and function as you'd expect, I think we'll be good to go.
jseppi: 👁‍🗨 Checking it out...

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d16f95c..2b42a9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,14 +2,24 @@
 
 ## HEAD
 
-- Add **Heading** component.
-- Add **Button** component.
-- Add **IconText** component.
-- Add **UnderlineTabs** component.
+- [feature] Add **Heading** component.
+- [feature] Add **Button** component.
+- [feature] Add **IconText** component.
+- [feature] Add **UnderlineTabs** component.
+- **Popover**
+  - 🚨 [breaking change] Remove `themePopover`, `backgroundColor`, and `accessibleTitle` props.
+  - 🚨 [breaking change] Rename `contentElementAttributes` prop to `passthroughProps`, to match incoming convention.
+  - [feature] Add `coloring` and `padded`.
+  - [chore] Update `focus-trap` and `tabbable` dependencies.
 - **PopoverTrigger**
-  - *Fix:* If the trigger responds to focus but not click, and you focus the trigger *first* and *then* click, that click *closes* the popover instead of leaving it open even after you move the mouse away.
+  - 🚨 [breaking change] Rename `triggerProps` to `passthroughTriggerProps`.
+  - [fix] If the trigger responds to focus but not click, and you focus the trigger *first* and *then* click, that click *closes* the popover instead of leaving it open even after you move the mouse away.
 - **CopyButton**
-  - *Fix:* Works in modals.
+  - [fix] Works in modals.
+- **Tooltip**
+  - 🚨 [breaking change] Remove `backgroundColor` and `themeTooltip` props.
+  - [feature] Add `coloring` and `padded` props.
+  - [feature] Children of Tooltip no longer need to be functions! If you provide a standard DOM element (e.g. `<button>`, `<div>`, `<span>`) or a Button component, things will just work. You can still use a function if your trigger is a custom component.
 
 ## 0.2.1
 
diff --git a/package-lock.json b/package-lock.json
index fc05d41..bec4498 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6914,11 +6914,12 @@
       }
     },
     "focus-trap": {
-      "version": "2.4.6",
-      "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-2.4.6.tgz",
-      "integrity": "sha512-vWZTPtBU6pBoyWZDRZJHkXsyP2ZCZBHE3DRVXnSVdQKH/mcDtu9S5Kz8CUDyIqpfZfLEyI9rjKJLnc4Y40BRBg==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-3.0.0.tgz",
+      "integrity": "sha512-jTFblf0tLWbleGjj2JZsAKbgtZTdL1uC48L8FcmSDl4c2vDoU4NycN1kgV5vJhuq1mxNFkw7uWZ1JAGlINWvyw==",
       "requires": {
-        "tabbable": "^1.0.3"
+        "tabbable": "^3.1.0",
+        "xtend": "^4.0.1"
       }
     },
     "focus-trap-react": {
@@ -6927,6 +6928,21 @@
       "integrity": "sha512-MoQmONoy9gRPyrC5DGezkcOMGgx7MtIOAQDHe098UtL2sA2vmucJwEmQisb+8LRXNYFHxuw5zJ1oLFeKu4Mteg==",
       "requires": {
         "focus-trap": "^2.0.1"
+      },
+      "dependencies": {
+        "focus-trap": {
+          "version": "2.4.6",
+          "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-2.4.6.tgz",
+          "integrity": "sha512-vWZTPtBU6pBoyWZDRZJHkXsyP2ZCZBHE3DRVXnSVdQKH/mcDtu9S5Kz8CUDyIqpfZfLEyI9rjKJLnc4Y40BRBg==",
+          "requires": {
+            "tabbable": "^1.0.3"
+          }
+        },
+        "tabbable": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-1.1.3.tgz",
+          "integrity": "sha512-nOWwx35/JuDI4ONuF0ZTo6lYvI0fY0tZCH1ErzY2EXfu4az50ZyiUX8X073FLiZtmWUVlkRnuXsehjJgCw9tYg=="
+        }
       }
     },
     "for-in": {
@@ -16620,9 +16636,9 @@
       }
     },
     "tabbable": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-1.1.3.tgz",
-      "integrity": "sha512-nOWwx35/JuDI4ONuF0ZTo6lYvI0fY0tZCH1ErzY2EXfu4az50ZyiUX8X073FLiZtmWUVlkRnuXsehjJgCw9tYg=="
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-3.1.0.tgz",
+      "integrity": "sha512-W+w8dbSbQiuhMQhJbgtSn7ksg1Z+Z0UQq0WpDuYUrXjqie2bkMTfXRhdKtyQ8lRbppDWFoQf+S3T+OBGxoEPUA=="
     },
     "table": {
       "version": "4.0.3",
diff --git a/package.json b/package.json
index c3948bc..58ba609 100644
--- a/package.json
+++ b/package.json
@@ -45,7 +45,7 @@
     "classnames": "^2.2.6",
     "clipboard": "^2.0.0",
     "debounce": "^1.1.0",
-    "focus-trap": "^2.3.0",
+    "focus-trap": "^3.0.0",
     "hoverintent": "^2.0.0",
     "os-key": "^1.0.0",
     "prefix": "^1.0.0",
@@ -55,7 +55,7 @@
     "react-submittable": "^2.0.0",
     "select": "^1.1.2",
     "shallow-equal": "^1.0.0",
-    "tabbable": "^1.1.1",
+    "tabbable": "^3.1.0",
     "xtend": "^4.0.0"
   },
   "devDependencies": {
diff --git a/src/.eslintrc.json b/src/.eslintrc.json
index dcf0076..b10cf9e 100644
--- a/src/.eslintrc.json
+++ b/src/.eslintrc.json
@@ -27,6 +27,12 @@
       "env": {
         "jest": true
       }
+    },
+    {
+      "files": ["**/examples/**.js"],
+      "rules": {
+        "react/prop-types": false
+      }
     }
   ]
 }
diff --git a/src/components/popover-trigger/examples/popover-trigger-example-basic.js b/src/components/popover-trigger/examples/popover-trigger-a.js
similarity index 71%
rename from src/components/popover-trigger/examples/popover-trigger-example-basic.js
rename to src/components/popover-trigger/examples/popover-trigger-a.js
index 63254bb..d3fa331 100644
--- a/src/components/popover-trigger/examples/popover-trigger-example-basic.js
+++ b/src/components/popover-trigger/examples/popover-trigger-a.js
@@ -1,19 +1,19 @@
 /*
-Basic
+A standard PopoverTrigger.
 */
 import React from 'react';
 import PopoverTrigger from '../popover-trigger';
 
 export default class Example extends React.Component {
   getPopoverContent = () => {
-    return <div>test popover</div>;
+    return <div>The popover has been opened</div>;
   };
 
   render() {
     return (
       <div>
         <PopoverTrigger content={this.getPopoverContent}>
-          <button type="button" className="btn">
+          <button type="button" className="btn btn--s btn--purple">
             Trigger
           </button>
         </PopoverTrigger>
diff --git a/src/components/popover-trigger/examples/popover-trigger-b.js b/src/components/popover-trigger/examples/popover-trigger-b.js
new file mode 100644
index 0000000..a95b821
--- /dev/null
+++ b/src/components/popover-trigger/examples/popover-trigger-b.js
@@ -0,0 +1,58 @@
+/*
+A PopoverTrigger that opens a dark popover and traps focus inside its content.
+*/
+import React from 'react';
+import PopoverTrigger from '../popover-trigger';
+
+export default class Example extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = { popoverOpen: false };
+    this.handlePopoverOpen = this.handlePopoverOpen.bind(this);
+    this.handlePopoverClose = this.handlePopoverClose.bind(this);
+  }
+
+  handlePopoverOpen() {
+    this.setState({ popoverOpen: true });
+  }
+
+  handlePopoverClose() {
+    this.setState({ popoverOpen: false });
+  }
+
+  getPopoverContent() {
+    return (
+      <div>
+        <label htmlFor="ptb-input" className="txt-bold txt-xs block mb3">
+          Type in here
+        </label>
+        <input id="ptb-input" className="input input--s bg-white" type="text" />
+      </div>
+    );
+  }
+
+  render() {
+    return (
+      <div>
+        <PopoverTrigger
+          content={this.getPopoverContent}
+          onPopoverOpen={this.handlePopoverOpen}
+          onPopoverClose={this.handlePopoverClose}
+          trapFocus={true}
+          popoverProps={{
+            coloring: 'dark',
+            alignment: 'center',
+            placement: 'top'
+          }}
+          passthroughTriggerProps={{
+            'data-test': 'trigger-container'
+          }}
+        >
+          <button type="button" className="btn btn--s">
+            Trigger
+          </button>
+        </PopoverTrigger>
+      </div>
+    );
+  }
+}
diff --git a/src/components/popover-trigger/examples/popover-trigger-example-options.js b/src/components/popover-trigger/examples/popover-trigger-example-options.js
deleted file mode 100644
index 8191824..0000000
--- a/src/components/popover-trigger/examples/popover-trigger-example-options.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
-More options
-*/
-import React from 'react';
-import PopoverTrigger from '../popover-trigger';
-
-export default class Example extends React.Component {
-  constructor(props) {
-    super(props);
-    this.state = { openPopover: false };
-  }
-
-  handlePopoverOpen = () => {
-    this.setState({ openPopover: true });
-  };
-
-  handlePopoverClose = () => {
-    this.setState({ openPopover: false });
-  };
-
-  getPopoverContent = () => {
-    return <input className="input" type="text" />;
-  };
-
-  render() {
-    return (
-      <div>
-        <PopoverTrigger
-          content={this.getPopoverContent}
-          onPopoverOpen={this.handlePopoverOpen}
-          onPopoverClose={this.handlePopoverClose}
-          respondsToFocus={true}
-          respondsToHover={true}
-          trapFocus={true}
-          popoverProps={{
-            backgroundColor: 'yellow'
-          }}
-          triggerProps={{
-            'data-test': 'trigger-container'
-          }}
-        >
-          <button type="button" className="btn">
-            Trigger
-          </button>
-        </PopoverTrigger>
-      </div>
-    );
-  }
-}
diff --git a/src/components/popover-trigger/popover-trigger.js b/src/components/popover-trigger/popover-trigger.js
index 0de5950..ce78fb0 100644
--- a/src/components/popover-trigger/popover-trigger.js
+++ b/src/components/popover-trigger/popover-trigger.js
@@ -7,53 +7,22 @@ const TRIGGER_HOVER = 'hover';
 const TRIGGER_FOCUS = 'focus';
 const TRIGGER_CLICK = 'click';
 
+/**
+ * A smart trigger for managing the state of a [Popover](#popover).
+ *
+ * If you want to manage the open-closed state of the Popover yourself,
+ * use [Popover](#popover) directly.
+ *
+ * `respondsToClick`, `respondsToHover`, and `respondsToFocus` props determine
+ * the means of opening and closing the Popover with interactions. If you open
+ * the Popover with a hover or focus, you can also close it by hovering away or
+ * blurring. If, however, you open the Popover with a click, you can only close
+ * it with another click, on the trigger or outside the Popover. If you
+ * open the Popover with a hover and *then* you click, this is equivalent to
+ * opening it with a click directly, so it will only close with another
+ * click (not from just hovering away).
+ */
 export default class PopoverTrigger extends React.Component {
-  static propTypes = {
-    /** The trigger content. This can either be a string or valid JSX. */
-    children: PropTypes.node.isRequired,
-    /** Accepts a CSS `display` value. */
-    display: PropTypes.string,
-    /** The popover content. This can either be a string, valid JSX, or a function returning either. */
-    content: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
-    /** If `true`, the popover will always be rendered, and will be hidden when inactive.
-     * By default, the popover is added to and removed from the DOM dynamically. This prop is used by Tooltip for accessbility reasons.
-     */
-    renderHiddenContent: PropTypes.bool,
-    /** Whether or not the popover receives focus when it opens. */
-    receiveFocus: PropTypes.bool,
-    /** Whether or not the popover receives *and traps* focus when it opens. */
-    trapFocus: PropTypes.bool,
-    /** Whether or not the trigger responds to clicks. **Warning**: You probably don't want to use `respondsToClick` and `receivesFocus` *and* `respondsToFocus` all together, because they can have clashing interactions. e.g. When the popover that trapped focus closes, it returns focus to the trigger, which then causes the popover to open again. */
-    respondsToClick: PropTypes.bool,
-    /** Whether or not the trigger responds to `mouseenter` and `mouseleave` events. */
-    respondsToHover: PropTypes.bool,
-    /** Whether or not the trigger responds to `focus` and `blur` events. */
-    respondsToFocus: PropTypes.bool,
-    /** If `true`, the trigger will not work. */
-    disabled: PropTypes.bool,
-    /** Extra props to pass to the Popover component. */
-    popoverProps: PropTypes.object,
-    /** Extra props to pass the `<div>` around your trigger content. */
-    triggerProps: PropTypes.object,
-    /** Callback that is invoked when the popover opens. */
-    onPopoverOpen: PropTypes.func,
-    /** Callback that is invoked when the popover closes. */
-    onPopoverClose: PropTypes.func
-  };
-
-  static defaultProps = {
-    display: 'inline-block',
-    disabled: false,
-    renderHiddenContent: false,
-    receiveFocus: true,
-    trapFocus: false,
-    respondsToClick: true,
-    respondsToHover: false,
-    respondsToFocus: false,
-    popoverProps: {},
-    triggerProps: {}
-  };
-
   state = {
     visible: false,
     activeTriggerType: null
@@ -364,9 +333,9 @@ export default class PopoverTrigger extends React.Component {
     if (state.visible) {
       // Only send focus inside if we've clicked to open
       const receiveFocus =
-        props.receiveFocus && state.activeTriggerType !== TRIGGER_CLICK;
+        props.receiveFocus && state.activeTriggerType === TRIGGER_CLICK;
       const trapFocus =
-        props.trapFocus && state.activeTriggerType !== TRIGGER_CLICK;
+        props.trapFocus && state.activeTriggerType === TRIGGER_CLICK;
 
       popover = (
         <Popover
@@ -390,7 +359,7 @@ export default class PopoverTrigger extends React.Component {
       hiddenContent = (
         <div
           className="hide-visually"
-          {...this.props.popoverProps.contentElementAttributes || {}}
+          {...this.props.popoverProps.passthroughProps || {}}
         >
           {this.getPopoverContent()}
         </div>
@@ -401,7 +370,7 @@ export default class PopoverTrigger extends React.Component {
       <div
         ref={this.setTriggerElement}
         style={{ display: props.display }}
-        {...props.triggerProps}
+        {...props.passthroughTriggerProps}
         onClick={this.onAnyClick}
         onFocus={this.onTriggerFocus}
         onBlur={this.onTriggerBlur}
@@ -414,3 +383,93 @@ export default class PopoverTrigger extends React.Component {
     );
   }
 }
+
+PopoverTrigger.propTypes = {
+  /**
+   * The trigger content. This can either be a string or valid JSX.
+   */
+  children: PropTypes.node.isRequired,
+  /**
+   * Accepts a CSS `display` value for the trigger: `'inline-block'` or
+   * `'block'`.
+   */
+  display: PropTypes.oneOf(['inline-block', 'block']),
+  /**
+   * The popover content. This can either be a string, valid JSX, or a function
+   * returning either.
+   */
+  content: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
+  /**
+   * **You will not need to use this.** This prop is used by
+   * [Tooltip](#tooltip) for improved accessbility.
+   *
+   * If `true`, the popover will always be rendered *but it will be visually
+   * hidden when inactive*.
+   *
+   * By default, the popover is added to and removed from the DOM dynamically.
+   */
+  renderHiddenContent: PropTypes.bool,
+  /**
+   * Whether or not the popover receives focus when it opens.
+   *
+   * This will *only* happen if the trigger is opened with a click.
+   */
+  receiveFocus: PropTypes.bool,
+  /**
+   * Whether or not the popover receives *and traps* focus when it opens.
+   *
+   * This will *only* happen if the trigger is opened with a click.
+   */
+  trapFocus: PropTypes.bool,
+  /**
+   * Whether or not the trigger responds to clicks.
+   *
+   * **Warning**: You probably don't want to use `respondsToClick` and
+   * `receivesFocus` *and* `respondsToFocus` all together, because they can
+   * have clashing interactions. For example, when the popover that trapped
+   * focus closes, it returns focus to the trigger, which then causes the
+   * popover to open again.
+   */
+  respondsToClick: PropTypes.bool,
+  /**
+   * Whether or not the trigger responds to `mouseenter` and `mouseleave` events.
+   */
+  respondsToHover: PropTypes.bool,
+  /**
+   * Whether or not the trigger responds to `focus` and `blur` events.
+   */
+  respondsToFocus: PropTypes.bool,
+  /**
+   * Whether or not the trigger is disabled.
+   */
+  disabled: PropTypes.bool,
+  /**
+   * Props to pass directly to the [Popover](#popover) component. See Popover's
+   * documentation for details.
+   */
+  popoverProps: PropTypes.object,
+  /**
+   * Props to pass directly to the `<div>` that will wrap your trigger content.
+   */
+  passthroughTriggerProps: PropTypes.object,
+  /**
+   * Callback that is invoked when the popover opens.
+   */
+  onPopoverOpen: PropTypes.func,
+  /**
+   * Callback that is invoked when the popover closes.
+   */
+  onPopoverClose: PropTypes.func
+};
+
+PopoverTrigger.defaultProps = {
+  display: 'inline-block',
+  disabled: false,
+  renderHiddenContent: false,
+  receiveFocus: true,
+  trapFocus: false,
+  respondsToClick: true,
+  respondsToHover: false,
+  respondsToFocus: false,
+  popoverProps: {}
+};
diff --git a/src/components/popover/examples/popover-a.js b/src/components/popover/examples/popover-a.js
new file mode 100644
index 0000000..95ebcb0
--- /dev/null
+++ b/src/components/popover/examples/popover-a.js
@@ -0,0 +1,63 @@
+/*
+Standard popover.
+*/
+import React from 'react';
+import Popover from '../popover';
+
+export default class Example extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = { popoverOpen: false };
+    this.togglePopover = this.togglePopover.bind(this);
+    this.setAnchor = this.setAnchor.bind(this);
+    this.getAnchor = this.getAnchor.bind(this);
+    this.ignore = this.ignore.bind(this);
+  }
+
+  togglePopover() {
+    this.setState(state => ({ popoverOpen: !state.popoverOpen }));
+  }
+
+  setAnchor(el) {
+    this.anchor = el;
+  }
+
+  getAnchor() {
+    return this.anchor;
+  }
+
+  ignore(el) {
+    return el === this.getAnchor();
+  }
+
+  renderPopover() {
+    if (!this.state.popoverOpen) {
+      return null;
+    }
+    return (
+      <Popover
+        getAnchorElement={this.getAnchor}
+        onExit={this.togglePopover}
+        ignoreClickWithinElement={this.ignore}
+        alignment="center"
+      >
+        This is a popover.
+      </Popover>
+    );
+  }
+
+  render() {
+    return (
+      <div>
+        <button
+          ref={this.setAnchor}
+          onClick={this.togglePopover}
+          className="btn btn--s"
+        >
+          Toggle popover
+        </button>
+        {this.renderPopover()}
+      </div>
+    );
+  }
+}
diff --git a/src/components/popover/examples/popover-b.js b/src/components/popover/examples/popover-b.js
new file mode 100644
index 0000000..26e3379
--- /dev/null
+++ b/src/components/popover/examples/popover-b.js
@@ -0,0 +1,64 @@
+/*
+Dark popover.
+*/
+import React from 'react';
+import Popover from '../popover';
+
+export default class Example extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = { popoverOpen: false };
+    this.togglePopover = this.togglePopover.bind(this);
+    this.setAnchor = this.setAnchor.bind(this);
+    this.getAnchor = this.getAnchor.bind(this);
+    this.ignore = this.ignore.bind(this);
+  }
+
+  togglePopover() {
+    this.setState(state => ({ popoverOpen: !state.popoverOpen }));
+  }
+
+  setAnchor(el) {
+    this.anchor = el;
+  }
+
+  getAnchor() {
+    return this.anchor;
+  }
+
+  ignore(el) {
+    return el === this.getAnchor();
+  }
+
+  renderPopover() {
+    if (!this.state.popoverOpen) {
+      return null;
+    }
+    return (
+      <Popover
+        coloring="dark"
+        getAnchorElement={this.getAnchor}
+        onExit={this.togglePopover}
+        ignoreClickWithinElement={this.ignore}
+        alignment="center"
+      >
+        This is a dark popover.
+      </Popover>
+    );
+  }
+
+  render() {
+    return (
+      <div>
+        <button
+          ref={this.setAnchor}
+          onClick={this.togglePopover}
+          className="btn btn--s"
+        >
+          Toggle popover
+        </button>
+        {this.renderPopover()}
+      </div>
+    );
+  }
+}
diff --git a/src/components/popover/examples/popover-c.js b/src/components/popover/examples/popover-c.js
new file mode 100644
index 0000000..695e7e0
--- /dev/null
+++ b/src/components/popover/examples/popover-c.js
@@ -0,0 +1,64 @@
+/*
+Warning popover.
+*/
+import React from 'react';
+import Popover from '../popover';
+
+export default class Example extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = { popoverOpen: false };
+    this.togglePopover = this.togglePopover.bind(this);
+    this.setAnchor = this.setAnchor.bind(this);
+    this.getAnchor = this.getAnchor.bind(this);
+    this.ignore = this.ignore.bind(this);
+  }
+
+  togglePopover() {
+    this.setState(state => ({ popoverOpen: !state.popoverOpen }));
+  }
+
+  setAnchor(el) {
+    this.anchor = el;
+  }
+
+  getAnchor() {
+    return this.anchor;
+  }
+
+  ignore(el) {
+    return el === this.getAnchor();
+  }
+
+  renderPopover() {
+    if (!this.state.popoverOpen) {
+      return null;
+    }
+    return (
+      <Popover
+        coloring="warning"
+        getAnchorElement={this.getAnchor}
+        onExit={this.togglePopover}
+        ignoreClickWithinElement={this.ignore}
+        alignment="center"
+      >
+        This is a warning popover.
+      </Popover>
+    );
+  }
+
+  render() {
+    return (
+      <div>
+        <button
+          ref={this.setAnchor}
+          onClick={this.togglePopover}
+          className="btn btn--s"
+        >
+          Toggle popover
+        </button>
+        {this.renderPopover()}
+      </div>
+    );
+  }
+}
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js
index 9f7cac0..1cad1f7 100644
--- a/src/components/popover/popover.js
+++ b/src/components/popover/popover.js
@@ -13,77 +13,19 @@ let popoverCounter = 0; // Incremented on creation
  * and the preferred position determined by props is adjusted according to the
  * available space.
  *
- * If there's a trigger that opens and closes the popover, you should consider
- * using PopoverTrigger, instead, which internally manages the Popover.
+ * **Usually you won't need to use this low-level component.** You should have
+ * a look at [Tooltip](#tooltip) and [PopoverTrigger](#popovertrigger).
  *
  * If you are using this component directly, you need to manage its open-closed
  * state. Use `onExit` to do that.
+ *
+ * The static function `Popover.repositionPopovers()` can be used to
+ * automatically reposition *all the popovers that are currently open*. This is
+ * useful if some state change other than a scroll or resize may have caused
+ * the popover's anchor to move or the space available to the
+ * popover to change.
  */
 export default class Popover extends React.Component {
-  static propTypes = {
-    /** A function that returns the DOM node to which this Popover should be anchored. `placement` and `alignment` values are related to this anchor. */
-    getAnchorElement: PropTypes.func.isRequired,
-    /** The content of the popover. */
-    children: PropTypes.node.isRequired,
-    /** Preferred placement of the popover in relation to the anchor. Adjusted according to available space. */
-    placement: PropTypes.oneOf(['top', 'bottom', 'left', 'right']),
-    /** Alignment of the popover relative to the side of the anchor on which it's placed. If `placement` is `left` or `right`, meaningful `alignment` values are `top`, `bottom`, and `center`. If `placement` is `top` or `bottom`, meaningful `alignment` values are `left`, `right`, and `center`. Adjusted according to available space. */
-    alignment: PropTypes.oneOf(['top', 'bottom', 'left', 'right', 'center']),
-    /** Background color of the popover. */
-    backgroundColor: PropTypes.string,
-    /** Pass custom classes to style the popover container. */
-    themePopover: PropTypes.string,
-    /** Whether or not the popover has a triangle pointer. */
-    hasPointer: PropTypes.bool,
-    /** If `true`, the popover will hide when its anchor is scroll offscreen. By default, the popover will follow its anchor. If the anchor is within an internally scrolling area, you may want to use `true`. */
-    hideWhenAnchorIsOffscreen: PropTypes.bool,
-    /** If `false`, the popover is not allowed to change axes on the anchor when modifying its position to fit available space. By default, popovers on the `left` and `right`, for example, might change to `bottom` is there is neither space on the left nor the right. */
-    allowPlacementAxisChange: PropTypes.bool,
-    /** If `false`, the tooltip is allowed to leave the viewport. By default, it will stick to the edge of the viewport as its anchor scrolls out of sight. */
-    containWithinViewport: PropTypes.bool,
-    /** If `false`, clicking outside the popver will not close it. By default, it does. */
-    clickOutsideCloses: PropTypes.bool,
-    /** If `false`, hitting Escape will not close the popover. By default, it does. */
-    escapeCloses: PropTypes.bool,
-    /** If `true`, the popover will not receive focus when it opens. By default, it does. */
-    receiveFocus: PropTypes.bool,
-    /** If `true`, the popover will receive *and trap* focus when it opens. */
-    trapFocus: PropTypes.bool,
-    /** A title for the popover that will be legible for screen readers. *You should use this.* It's optional, though, because Tooltip does not need it, but attains accessibility by other means. */
-    accessibleTitle: PropTypes.string,
-    /** A function called when popover is dismissed. You need to use this callback to remove the Popover from the rendered page. */
-    onExit: PropTypes.func,
-    /** A function to call as soon as popover element is rendered. Returns the DOM node of the popover body.  */
-    onElement: PropTypes.func,
-    /** A function that returns a DOM node. Use to specify which element is focused when popover is first rendered. */
-    getInitialFocus: PropTypes.func,
-    /** A function called when an element within the popover is clicked. */
-    ignoreClickWithinElement: PropTypes.func,
-    /** A function that returns a DOM node that should contain the popover within it. The popover's position will be calculated relative to this container, rather than the viewport. */
-    getContainingElement: PropTypes.func,
-    /** See options for calculatePopoverPosition. */
-    offsetFromAnchor: PropTypes.number,
-    /** Attributes to pass to the `<div>` that will wrap your popover content. */
-    contentElementAttributes: PropTypes.object
-  };
-
-  static defaultProps = {
-    placement: 'right',
-    alignment: 'top',
-    backgroundColor: '#fff',
-    themePopover: 'round py6 px12 shadow-darken25',
-    hasPointer: true,
-    hideWhenAnchorIsOffscreen: false,
-    allowPlacementAxisChange: true,
-    containWithinViewport: true,
-    clickOutsideCloses: true,
-    escapeCloses: true,
-    receiveFocus: true,
-    trapFocus: false
-  };
-
-  static repositionPopovers = PopoverPositioner.recalculatePositions;
-
   constructor(props) {
     super(props);
     popoverCounter += 1;
@@ -242,37 +184,189 @@ export default class Popover extends React.Component {
   };
 
   render() {
+    const { props } = this;
+    const colors = getColors(props.coloring);
+    let bodyClasses = `${colors.textClass} shadow-darken25 round`;
+    if (props.padded) {
+      bodyClasses += ' px12 py12';
+    }
+
     return (
       <PopoverPositioner
-        getAnchorElement={this.props.getAnchorElement}
-        hasPointer={this.props.hasPointer}
-        placement={this.props.placement}
-        alignment={this.props.alignment}
-        pointerColor={this.props.backgroundColor}
-        hideWhenAnchorIsOffscreen={this.props.hideWhenAnchorIsOffscreen}
-        allowPlacementAxisChange={this.props.allowPlacementAxisChange}
-        containWithinViewport={this.props.containWithinViewport}
-        getContainingElement={this.props.getContainingElement}
-        offsetFromAnchor={this.props.offsetFromAnchor}
+        getAnchorElement={props.getAnchorElement}
+        hasPointer={props.hasPointer}
+        placement={props.placement}
+        alignment={props.alignment}
+        pointerColor={colors.background}
+        hideWhenAnchorIsOffscreen={props.hideWhenAnchorIsOffscreen}
+        allowPlacementAxisChange={props.allowPlacementAxisChange}
+        containWithinViewport={props.containWithinViewport}
+        getContainingElement={props.getContainingElement}
+        offsetFromAnchor={props.offsetFromAnchor}
         onElement={this.setPopoverElement}
       >
         <div
           key="body"
-          style={{ backgroundColor: this.props.backgroundColor }}
-          className={this.props.themePopover}
+          style={{ backgroundColor: colors.background }}
+          className={bodyClasses}
           onKeyDown={this.onBodyKeyDown}
           onFocus={this.onBodyFocus}
           data-popover-id={this.popoverId}
           tabIndex={-1}
-          aria-label={this.props.accessibleTitle}
           role="dialog"
-          {...this.props.contentElementAttributes}
+          {...props.passthroughProps}
         >
-          {this.props.children}
+          {props.children}
         </div>
       </PopoverPositioner>
     );
   }
 }
 
+Popover.propTypes = {
+  /**
+   * A function that returns the DOM node to which this Popover should be
+   * anchored. `placement` and `alignment` values are related to this anchor.
+   */
+  getAnchorElement: PropTypes.func.isRequired,
+  /**
+   * The content of the popover.
+   */
+  children: PropTypes.node.isRequired,
+  /**
+   * Preferred placement of the popover in relation to the anchor.
+   * Adjusted according to available space.
+   */
+  placement: PropTypes.oneOf(['top', 'bottom', 'left', 'right']),
+  /**
+   * Alignment of the popover relative to the side of the anchor on which
+   * it's placed. If `placement` is `left` or `right`, meaningful `alignment`
+   * values are `top`, `bottom`, and `center`. If `placement` is `top` or
+   * `bottom`, meaningful `alignment` values are `left`, `right`, and `center`.
+   * Adjusted according to available space.
+   */
+  alignment: PropTypes.oneOf(['top', 'bottom', 'left', 'right', 'center']),
+  /**
+   * `'light'`, `'dark'`, or `'warning'`.
+   */
+  coloring: PropTypes.oneOf(['light', 'dark', 'warning']),
+  /**
+   * Set to `false` to remove the default padding on the popover body
+   * (and, hopefully, apply your own).
+   */
+  padded: PropTypes.bool,
+  /**
+   * Whether or not the popover has a triangle pointer.
+   */
+  hasPointer: PropTypes.bool,
+  /**
+   * If `true`, the popover will hide when its anchor is scrolled offscreen.
+   * By default, the popover will follow its anchor wherever it goes.
+   *
+   * If your anchor is within an  internally scrolling area, you may want to
+   * use `true`, so the popover doesn't existing in a disembodied state after
+   * its anchor is scrolled away.
+   */
+  hideWhenAnchorIsOffscreen: PropTypes.bool,
+  /**
+   * If `false`, the popover is *not* allowed to change axes on the anchor when
+   * modifying its position to fit available space. By default, popovers on the
+   * `left` and `right`, for example, might change to `bottom` is there is
+   * neither space on the left nor the right.
+   */
+  allowPlacementAxisChange: PropTypes.bool,
+  /**
+   * If `false`, the tooltip is allowed to leave the viewport.
+   * By default, it will stick to the edge of the viewport as its anchor
+   * scrolls out of sight.
+   */
+  containWithinViewport: PropTypes.bool,
+  /**
+   * If `false`, clicking outside the popver will not close it.
+   * By default, it does.
+   */
+  clickOutsideCloses: PropTypes.bool,
+  /**
+   * If `false`, hitting Escape will not close the popover. By default, it does.
+   */
+  escapeCloses: PropTypes.bool,
+  /**
+   * If `true`, the popover will not receive focus when it opens.
+   */
+  receiveFocus: PropTypes.bool,
+  /**
+   * If `true`, the popover will receive *and trap* focus when it opens.
+   */
+  trapFocus: PropTypes.bool,
+  /**
+   * A function called when popover is dismissed. You need to use this callback
+   * to remove the Popover from the rendered page.
+   */
+  onExit: PropTypes.func,
+  /**
+   * A function to call as soon as popover element is rendered. Returns the DOM
+   * node of the popover body.
+   */
+  onElement: PropTypes.func,
+  /**
+   * A function that returns a DOM node. Use to specify which element should
+   * receive focus when popover is first rendered.
+   */
+  getInitialFocus: PropTypes.func,
+  /**
+   * A function called when an element within the popover is clicked.
+   */
+  ignoreClickWithinElement: PropTypes.func,
+  /**
+   * A function that returns a DOM node that should contain the popover within
+   * it. The popover's position will be calculated relative to this container,
+   * rather than the viewport.
+   */
+  getContainingElement: PropTypes.func,
+  /**
+   * Number of pixels by which the popover should be offset from its anchor.
+   */
+  offsetFromAnchor: PropTypes.number,
+  /**
+   * Props to pass directly to the `<div>` that will wrap your popover content.
+   */
+  passthroughProps: PropTypes.object
+};
+
+Popover.defaultProps = {
+  coloring: 'light',
+  placement: 'right',
+  alignment: 'top',
+  padded: true,
+  hasPointer: true,
+  hideWhenAnchorIsOffscreen: false,
+  allowPlacementAxisChange: true,
+  containWithinViewport: true,
+  clickOutsideCloses: true,
+  escapeCloses: true,
+  receiveFocus: true,
+  trapFocus: false
+};
+
+Popover.repositionPopovers = PopoverPositioner.recalculatePositions;
+
 function noop() {}
+
+function getColors(coloring) {
+  if (coloring === 'dark') {
+    return {
+      background: '#273d56', // gray-dark
+      textClass: 'color-white'
+    };
+  }
+  if (coloring === 'warning') {
+    return {
+      background: '#FFF5A0', // yellow-light
+      textClass: 'color-text'
+    };
+  }
+  return {
+    background: '#fff',
+    textClass: 'color-text'
+  };
+}
diff --git a/src/components/tooltip/examples/tooltip-example-basic.js b/src/components/tooltip/examples/tooltip-a.js
similarity index 51%
rename from src/components/tooltip/examples/tooltip-example-basic.js
rename to src/components/tooltip/examples/tooltip-a.js
index 054690f..15f1e75 100644
--- a/src/components/tooltip/examples/tooltip-example-basic.js
+++ b/src/components/tooltip/examples/tooltip-a.js
@@ -1,5 +1,5 @@
 /*
-Basic
+A standard tooltip.
 */
 import React from 'react';
 import Tooltip from '../tooltip';
@@ -7,12 +7,8 @@ import Tooltip from '../tooltip';
 export default class Example extends React.Component {
   render() {
     return (
-      <Tooltip content="Basic">
-        {triggerProps => (
-          <button className="btn" {...triggerProps}>
-            Basic
-          </button>
-        )}
+      <Tooltip content="Here's your tooltip">
+        <button className="btn">Basic</button>
       </Tooltip>
     );
   }
diff --git a/src/components/tooltip/examples/tooltip-b.js b/src/components/tooltip/examples/tooltip-b.js
new file mode 100644
index 0000000..e84a09c
--- /dev/null
+++ b/src/components/tooltip/examples/tooltip-b.js
@@ -0,0 +1,38 @@
+/*
+A dark tooltip with custom placement, which uses a function child
+to render a custom component as  the trigger and a function for its
+`content` prop.
+*/
+import React from 'react';
+import Tooltip from '../tooltip';
+
+export default class Example extends React.Component {
+  renderTooltipContent = () => {
+    return (
+      <span>
+        Some words with <span className="txt-bold">some style</span>
+      </span>
+    );
+  };
+
+  render() {
+    return (
+      <Tooltip
+        content={this.renderTooltipContent}
+        placement="bottom"
+        alignment="left"
+        coloring="dark"
+      >
+        {triggerProps => <CustomTrigger spanProps={triggerProps} />}
+      </Tooltip>
+    );
+  }
+}
+
+function CustomTrigger(props) {
+  return (
+    <span className="txt-h4 txt-fancy cursor-default" {...props.spanProps}>
+      custom trigger
+    </span>
+  );
+}
diff --git a/src/components/tooltip/examples/tooltip-example-options.js b/src/components/tooltip/examples/tooltip-example-options.js
deleted file mode 100644
index b118047..0000000
--- a/src/components/tooltip/examples/tooltip-example-options.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
-Clickable tooltip
-*/
-import React from 'react';
-import Tooltip from '../tooltip';
-
-export default class Example extends React.Component {
-  renderTooltipContent = () => {
-    return <span>Some words</span>;
-  };
-
-  render() {
-    return (
-      <Tooltip
-        content={this.renderTooltipContent}
-        placement="top"
-        alignment="left"
-        backgroundColor="red"
-        respondsToClick={true}
-        themeTooltip="py6 px12 round txt-s txt-bold"
-        display="inline"
-      >
-        {triggerProps => (
-          <button className="btn" type="button" {...triggerProps}>
-            Clickable
-          </button>
-        )}
-      </Tooltip>
-    );
-  }
-}
diff --git a/src/components/tooltip/tooltip.js b/src/components/tooltip/tooltip.js
index bfe1c31..3aac2c3 100644
--- a/src/components/tooltip/tooltip.js
+++ b/src/components/tooltip/tooltip.js
@@ -1,49 +1,18 @@
 import React from 'react';
+import classnames from 'classnames';
 import PropTypes from 'prop-types';
 import PopoverTrigger from '../popover-trigger';
 
 let tooltipCounter = 0;
 
+/**
+ * Wrap a trigger element so that when it is hovered or focused a tooltip
+ * appears.
+ *
+ * Tooltip extends [PopoverTrigger](#popovertrigger) and configures the
+ * popover and trigger for accessibility.
+ */
 export default class Tooltip extends React.Component {
-  static propTypes = {
-    /** The trigger element. Note that this should be a *function* that is passed a `triggerProps` argument to support accessiblity attributes from the tooltip. */
-    children: PropTypes.func.isRequired,
-    /** The tooltip content. This can either be a string, valid JSX, or a function returning either. */
-    content: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
-    /** Placement of the tooltip in relation to the trigger element. */
-    placement: PropTypes.oneOf(['top', 'bottom', 'left', 'right']),
-    /** Alignment of the tooltip's edge in relation to the trigger element. */
-    alignment: PropTypes.oneOf(['top', 'bottom', 'left', 'right', 'center']),
-    /** If `true`, the tooltip will not appear. */
-    disabled: PropTypes.bool,
-    /** Background color of the tooltip. */
-    backgroundColor: PropTypes.string,
-    /** If `true`, tooltip can be opened with a mouse click. */
-    respondsToClick: PropTypes.bool,
-    /** Classes to style the tooltip container. */
-    themeTooltip: PropTypes.string,
-    /** The CSS `display` property of the trigger element. */
-    display: PropTypes.string,
-    /** Added as `data-test` to the tooltip element. */
-    testId: PropTypes.string
-  };
-
-  state: {
-    visible: boolean,
-    tooltipId: ?string
-  };
-  anchorElement: ?Element;
-  showTimeout: ?number;
-
-  static defaultProps = {
-    placement: 'top',
-    alignment: 'center',
-    backgroundColor: '#fff',
-    disabled: false,
-    respondsToClick: false,
-    themeTooltip: 'round py6 px12 shadow-darken25 txt-s z1'
-  };
-
   state = {
     visible: false,
     tooltipId: null
@@ -58,48 +27,155 @@ export default class Tooltip extends React.Component {
     this.setState({ tooltipId: `tooltip-${tooltipCounter}` });
   }
 
+  getContent() {
+    const { content } = this.props;
+    if (typeof content === 'function') {
+      return content();
+    } else {
+      return content;
+    }
+  }
+
+  renderTrigger() {
+    const { children, testId } = this.props;
+    const { tooltipId } = this.state;
+
+    if (!children) {
+      return null;
+    }
+
+    const triggerProps = { 'aria-describedby': tooltipId };
+    if (testId) {
+      triggerProps['data-test'] = `${testId}-trigger`;
+    }
+
+    if (typeof children === 'function') {
+      return children(triggerProps);
+    }
+    if (children.type && typeof children.type === 'string') {
+      return React.cloneElement(children, triggerProps);
+    }
+    if (children.type && children.type.name === 'Button') {
+      return React.cloneElement(children, { passthroughProps: triggerProps });
+    }
+    throw new Error(
+      'Tooltip requires a child that is a function, a regular DOM node, or a Button. If your trigger is a different custom component, pass a function as the child.'
+    );
+  }
+
   render() {
-    const triggerProps = { 'aria-describedby': this.state.tooltipId };
-    const tooltipContentAttributes = {
-      id: this.state.tooltipId,
+    const { props, state } = this;
+
+    const popoverPassthroughProps = {
+      id: state.tooltipId,
       // Override the "dialog" role set by popover
       role: 'tooltip'
     };
 
-    const { testId } = this.props;
-    if (testId) {
-      triggerProps['data-test'] = `${testId}-trigger`;
-      tooltipContentAttributes['data-test'] = `${testId}-tooltip`;
+    if (props.testId) {
+      popoverPassthroughProps['data-test'] = `${props.testId}-tooltip`;
     }
 
-    const triggerElement = this.props.children
-      ? this.props.children(triggerProps)
-      : null;
+    const bodyClasses = classnames({
+      'px12 py6': props.padded,
+      'txt-s': props.textSize === 's',
+      'txt-xs': props.textSize === 'xs'
+    });
+
+    const content = <div className={bodyClasses}>{this.getContent()}</div>;
 
     return (
       <PopoverTrigger
-        content={this.props.content}
+        content={content}
         renderHiddenContent={true}
-        disabled={this.props.disabled}
-        display={this.props.display}
-        respondsToClick={this.props.respondsToClick}
+        disabled={props.disabled}
+        display={props.display}
+        respondsToClick={props.respondsToClick}
         respondsToHover={true}
         respondsToFocus={true}
         receiveFocus={false}
         popoverProps={{
           hasPointer: true,
-          themePopover: this.props.themeTooltip,
-          placement: this.props.placement,
-          alignment: this.props.alignment,
-          backgroundColor: this.props.backgroundColor,
+          placement: props.placement,
+          alignment: props.alignment,
+          coloring: props.coloring,
+          padded: false,
           hideWhenAnchorIsOffscreen: true,
-          // Tooltips do not need aria-labels
-          accessibleTitle: null,
-          contentElementAttributes: tooltipContentAttributes
+          passthroughProps: popoverPassthroughProps
         }}
       >
-        {triggerElement}
+        {this.renderTrigger()}
       </PopoverTrigger>
     );
   }
 }
+
+Tooltip.propTypes = {
+  /**
+   * The trigger element.
+   *
+   * If your element is a DOM element
+   * (e.g. `<button>`, `<a>`, `<span>`, `<div>`),
+   * you can pass it directly and it will have accessibility-related props
+   * automatically added to it.
+   *
+   * If your element is a custom React component, you need to provide a
+   * function as the child. The function will be invoked with a `triggerProps`
+   * object, and you need to apply those `triggerProps` in such a way that
+   * they end up on the DOM element of the trigger.
+   */
+  children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
+  /**
+   * The tooltip content. This can either be a string, valid JSX, or a function
+   * returning either.
+   */
+  content: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
+  /**
+   * Placement of the tooltip in relation to the trigger element.
+   */
+  placement: PropTypes.oneOf(['top', 'bottom', 'left', 'right']),
+  /**
+   * Alignment of the tooltip's edge in relation to the trigger element.
+   */
+  alignment: PropTypes.oneOf(['top', 'bottom', 'left', 'right', 'center']),
+  /**
+   * If `true`, the tooltip will not appear.
+   */
+  disabled: PropTypes.bool,
+  /**
+   * `'light'`, `'dark'`, or `'warning'`.
+   */
+  coloring: PropTypes.oneOf(['light', 'dark', 'warning']),
+  /**
+   * `'xs'` (extra small), `'s'` (small), or `'none'` (no size class).
+   */
+  textSize: PropTypes.oneOf(['xs', 's', 'none']),
+  /**
+   * Set to `false` to remove the default padding on the popover body
+   * (and, hopefully, apply your own).
+   */
+  padded: PropTypes.bool,
+  /**
+   * If `true`, tooltip can be opened with a mouse click.
+   */
+  respondsToClick: PropTypes.bool,
+  /**
+   * The CSS `display` value of the trigger element: '`block'` or '`inline-block'`.
+   */
+  display: PropTypes.oneOf(['block', 'inline-block']),
+  /**
+   * Added as `data-test` to the tooltip element.
+   */
+  testId: PropTypes.string
+};
+
+Tooltip.defaultProps = {
+  placement: 'top',
+  alignment: 'center',
+  coloring: 'light',
+  disabled: false,
+  respondsToClick: false,
+  padded: true,
+  display: 'inline-block',
+  textSize: 's'
+};


Test patch:
diff --git a/src/components/copiable/__tests__/__snapshots__/copiable.test.js.snap b/src/components/copiable/__tests__/__snapshots__/copiable.test.js.snap
index 6b22532..d33de59 100644
--- a/src/components/copiable/__tests__/__snapshots__/copiable.test.js.snap
+++ b/src/components/copiable/__tests__/__snapshots__/copiable.test.js.snap
@@ -59,16 +59,16 @@ exports[`Copiable basic Copy hint disappears at the right time 1`] = `
     accessibleTitle="⌘+C to copy!"
     alignment="center"
     allowPlacementAxisChange={true}
-    backgroundColor="#fff"
     clickOutsideCloses={true}
+    coloring="light"
     containWithinViewport={true}
     escapeCloses={true}
     getAnchorElement={[Function]}
     hasPointer={true}
     hideWhenAnchorIsOffscreen={true}
+    padded={true}
     placement="top"
     receiveFocus={true}
-    themePopover="round py6 px12 shadow-darken25"
     trapFocus={false}
   >
     <div
@@ -168,16 +168,16 @@ exports[`Copiable basic shows copy hint when clicked 1`] = `
     accessibleTitle="⌘+C to copy!"
     alignment="center"
     allowPlacementAxisChange={true}
-    backgroundColor="#fff"
     clickOutsideCloses={true}
+    coloring="light"
     containWithinViewport={true}
     escapeCloses={true}
     getAnchorElement={[Function]}
     hasPointer={true}
     hideWhenAnchorIsOffscreen={true}
+    padded={true}
     placement="top"
     receiveFocus={true}
-    themePopover="round py6 px12 shadow-darken25"
     trapFocus={false}
   >
     <div
diff --git a/src/components/copy-button/__tests__/__snapshots__/copy-button.test.js.snap b/src/components/copy-button/__tests__/__snapshots__/copy-button.test.js.snap
index 652a8e3..78f9220 100644
--- a/src/components/copy-button/__tests__/__snapshots__/copy-button.test.js.snap
+++ b/src/components/copy-button/__tests__/__snapshots__/copy-button.test.js.snap
@@ -17,16 +17,16 @@ exports[`CopyButton all props changes state on click 1`] = `
       accessibleTitle={null}
       alignment="center"
       allowPlacementAxisChange={true}
-      backgroundColor="#fff"
       clickOutsideCloses={true}
+      coloring="light"
       containWithinViewport={true}
       escapeCloses={true}
       getAnchorElement={[Function]}
       hasPointer={true}
       hideWhenAnchorIsOffscreen={true}
+      padded={true}
       placement="top"
       receiveFocus={true}
-      themePopover="round py6 px12 shadow-darken25"
       trapFocus={false}
     >
       <span
@@ -56,16 +56,16 @@ exports[`CopyButton all props changes state on click 2`] = `
       accessibleTitle={null}
       alignment="center"
       allowPlacementAxisChange={true}
-      backgroundColor="#fff"
       clickOutsideCloses={true}
+      coloring="light"
       containWithinViewport={true}
       escapeCloses={true}
       getAnchorElement={[Function]}
       hasPointer={true}
       hideWhenAnchorIsOffscreen={true}
+      padded={true}
       placement="top"
       receiveFocus={true}
-      themePopover="round py6 px12 shadow-darken25"
       trapFocus={false}
     >
       <span
@@ -117,16 +117,16 @@ exports[`CopyButton basic changes state on click 1`] = `
       accessibleTitle={null}
       alignment="center"
       allowPlacementAxisChange={true}
-      backgroundColor="#fff"
       clickOutsideCloses={true}
+      coloring="light"
       containWithinViewport={true}
       escapeCloses={true}
       getAnchorElement={[Function]}
       hasPointer={true}
       hideWhenAnchorIsOffscreen={true}
+      padded={true}
       placement="top"
       receiveFocus={true}
-      themePopover="round py6 px12 shadow-darken25"
       trapFocus={false}
     >
       <span
diff --git a/src/components/icon-button/__tests__/__snapshots__/icon-button.test.js.snap b/src/components/icon-button/__tests__/__snapshots__/icon-button.test.js.snap
index 1649b30..1f57d6b 100644
--- a/src/components/icon-button/__tests__/__snapshots__/icon-button.test.js.snap
+++ b/src/components/icon-button/__tests__/__snapshots__/icon-button.test.js.snap
@@ -4,6 +4,7 @@ exports[`IconButton all props used, JSX content renders 1`] = `
 <Tooltip
   alignment="right"
   backgroundColor="yellow"
+  coloring="light"
   content={
     <span
       className="txt-bold"
@@ -13,9 +14,11 @@ exports[`IconButton all props used, JSX content renders 1`] = `
   }
   disabled={false}
   display="inline-flex"
+  padded={true}
   placement="right"
   respondsToClick={true}
   testId="tooltip-test-id"
+  textSize="s"
   themeTooltip="color-gray round px12 py12 shadow-darken25"
 />
 `;
@@ -38,12 +41,14 @@ exports[`IconButton all props used, JSX content renders 2`] = `
 exports[`IconButton basic renders 1`] = `
 <Tooltip
   alignment="center"
-  backgroundColor="#fff"
+  coloring="light"
   content="close"
   disabled={false}
+  display="inline-block"
+  padded={true}
   placement="top"
   respondsToClick={false}
-  themeTooltip="round py6 px12 shadow-darken25 txt-s z1"
+  textSize="s"
 />
 `;
 
diff --git a/src/components/popover-trigger/__tests__/popover-trigger-test-cases.js b/src/components/popover-trigger/__tests__/popover-trigger-test-cases.js
index 041193d..d79aea5 100644
--- a/src/components/popover-trigger/__tests__/popover-trigger-test-cases.js
+++ b/src/components/popover-trigger/__tests__/popover-trigger-test-cases.js
@@ -51,8 +51,12 @@ testCases.callbacks = {
   component: PopoverTrigger,
   props: {
     content: getPopoverContent,
-    onPopoverOpen: safeSpy(),
-    onPopoverClose: safeSpy(),
+    onPopoverOpen: safeSpy(() => {
+      console.log('open');
+    }),
+    onPopoverClose: safeSpy(() => {
+      console.log('close');
+    }),
     respondsToFocus: true,
     respondsToHover: true,
     children: <button className="btn">Trigger</button>
diff --git a/src/components/popover/__tests__/__snapshots__/popover.test.js.snap b/src/components/popover/__tests__/__snapshots__/popover.test.js.snap
index 7dbdabd..b72edac 100644
--- a/src/components/popover/__tests__/__snapshots__/popover.test.js.snap
+++ b/src/components/popover/__tests__/__snapshots__/popover.test.js.snap
@@ -12,12 +12,11 @@ exports[`Popover all props renders 1`] = `
   mounted={true}
   onElement={[Function]}
   placement="left"
-  pointerColor="red"
+  pointerColor="#fff"
 >
   <div
-    aria-label="Title that is accessible"
-    className="px12 py12"
-    data-popover-id={2}
+    className="color-text shadow-darken25 round px12 py12"
+    data-popover-id={4}
     data-test={true}
     key="body"
     onFocus={[Function]}
@@ -25,7 +24,7 @@ exports[`Popover all props renders 1`] = `
     role="dialog"
     style={
       Object {
-        "backgroundColor": "red",
+        "backgroundColor": "#fff",
       }
     }
     tabIndex={-1}
@@ -53,7 +52,7 @@ exports[`Popover basic renders 1`] = `
   pointerColor="#fff"
 >
   <div
-    className="round py6 px12 shadow-darken25"
+    className="color-text shadow-darken25 round px12 py12"
     data-popover-id={1}
     key="body"
     onFocus={[Function]}
@@ -74,3 +73,75 @@ exports[`Popover basic renders 1`] = `
   </div>
 </Displaced>
 `;
+
+exports[`Popover dark renders 1`] = `
+<Displaced
+  alignment="top"
+  allowPlacementAxisChange={true}
+  containWithinViewport={true}
+  getAnchorElement={[Function]}
+  hasPointer={true}
+  hideWhenAnchorIsOffscreen={false}
+  mounted={true}
+  onElement={[Function]}
+  placement="right"
+  pointerColor="#273d56"
+>
+  <div
+    className="color-white shadow-darken25 round px12 py12"
+    data-popover-id={2}
+    key="body"
+    onFocus={[Function]}
+    onKeyDown={[Function]}
+    role="dialog"
+    style={
+      Object {
+        "backgroundColor": "#273d56",
+      }
+    }
+    tabIndex={-1}
+  >
+    <div
+      className="w240 h240"
+    >
+      test popover
+    </div>
+  </div>
+</Displaced>
+`;
+
+exports[`Popover warning renders 1`] = `
+<Displaced
+  alignment="top"
+  allowPlacementAxisChange={true}
+  containWithinViewport={true}
+  getAnchorElement={[Function]}
+  hasPointer={true}
+  hideWhenAnchorIsOffscreen={false}
+  mounted={true}
+  onElement={[Function]}
+  placement="right"
+  pointerColor="#FFF5A0"
+>
+  <div
+    className="color-text shadow-darken25 round px12 py12"
+    data-popover-id={3}
+    key="body"
+    onFocus={[Function]}
+    onKeyDown={[Function]}
+    role="dialog"
+    style={
+      Object {
+        "backgroundColor": "#FFF5A0",
+      }
+    }
+    tabIndex={-1}
+  >
+    <div
+      className="w240 h240"
+    >
+      test popover
+    </div>
+  </div>
+</Displaced>
+`;
diff --git a/src/components/popover/__tests__/popover-test-cases.js b/src/components/popover/__tests__/popover-test-cases.js
index 2e6aaf4..9840a70 100644
--- a/src/components/popover/__tests__/popover-test-cases.js
+++ b/src/components/popover/__tests__/popover-test-cases.js
@@ -230,8 +230,33 @@ testCases.trapFocus = {
   )
 };
 
-testCases.basic = {
-  noDisplay: true,
+testCases.dark = {
+  description: 'Dark',
+  element: (
+    <PopoverTrigger
+      popoverProps={{
+        coloring: 'dark'
+      }}
+      content="Dark coloring"
+    />
+  )
+};
+
+testCases.warning = {
+  description: 'Warning',
+  element: (
+    <PopoverTrigger
+      popoverProps={{
+        coloring: 'warning'
+      }}
+      content="Warning coloring"
+    />
+  )
+};
+
+const noDisplayCases = {};
+
+noDisplayCases.basic = {
   description: 'basic',
   component: Popover,
   props: {
@@ -240,16 +265,34 @@ testCases.basic = {
   }
 };
 
-testCases.allProps = {
-  noDisplay: true,
+noDisplayCases.dark = {
+  description: 'dark',
+  component: Popover,
+  props: {
+    getAnchorElement: () => {},
+    children: getPopoverContent(),
+    coloring: 'dark'
+  }
+};
+
+noDisplayCases.warning = {
+  description: 'warning',
+  component: Popover,
+  props: {
+    getAnchorElement: () => {},
+    children: getPopoverContent(),
+    coloring: 'warning'
+  }
+};
+
+noDisplayCases.allProps = {
   description: 'all props',
   component: Popover,
   props: {
     getAnchorElement: () => {},
     children: getPopoverContent(),
     placement: 'left',
-    backgroundColor: 'red',
-    themePopover: 'px12 py12',
+    padding: false,
     hasPointer: false,
     hideWhenAnchorIsOffscreen: true,
     allowPlacementAxisChange: false,
@@ -257,14 +300,13 @@ testCases.allProps = {
     clickOutsideCloses: false,
     escapeCloses: false,
     receiveFocus: false,
-    accessibleTitle: 'Title that is accessible',
     onExit: () => {},
     ignoreClickWithinElement: () => {},
     getContainingElement: () => {},
-    contentElementAttributes: {
+    passthroughProps: {
       'data-test': true
     }
   }
 };
 
-export { testCases };
+export { testCases, noDisplayCases };
diff --git a/src/components/popover/__tests__/popover.test.js b/src/components/popover/__tests__/popover.test.js
index 89acc12..eea6fa0 100644
--- a/src/components/popover/__tests__/popover.test.js
+++ b/src/components/popover/__tests__/popover.test.js
@@ -1,14 +1,14 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { testCases } from './popover-test-cases';
+import { noDisplayCases } from './popover-test-cases';
 
 describe('Popover', () => {
   let testCase;
   let wrapper;
 
-  describe(testCases.basic.description, () => {
+  describe(noDisplayCases.basic.description, () => {
     beforeEach(() => {
-      testCase = testCases.basic;
+      testCase = noDisplayCases.basic;
       wrapper = shallow(
         React.createElement(testCase.component, testCase.props)
       );
@@ -19,9 +19,35 @@ describe('Popover', () => {
     });
   });
 
-  describe(testCases.allProps.description, () => {
+  describe(noDisplayCases.dark.description, () => {
     beforeEach(() => {
-      testCase = testCases.allProps;
+      testCase = noDisplayCases.dark;
+      wrapper = shallow(
+        React.createElement(testCase.component, testCase.props)
+      );
+    });
+
+    test('renders', () => {
+      expect(wrapper).toMatchSnapshot();
+    });
+  });
+
+  describe(noDisplayCases.warning.description, () => {
+    beforeEach(() => {
+      testCase = noDisplayCases.warning;
+      wrapper = shallow(
+        React.createElement(testCase.component, testCase.props)
+      );
+    });
+
+    test('renders', () => {
+      expect(wrapper).toMatchSnapshot();
+    });
+  });
+
+  describe(noDisplayCases.allProps.description, () => {
+    beforeEach(() => {
+      testCase = noDisplayCases.allProps;
       wrapper = shallow(
         React.createElement(testCase.component, testCase.props)
       );
diff --git a/src/components/tooltip/__tests__/__snapshots__/tooltip.test.js.snap b/src/components/tooltip/__tests__/__snapshots__/tooltip.test.js.snap
index e2600a6..87dc6b3 100644
--- a/src/components/tooltip/__tests__/__snapshots__/tooltip.test.js.snap
+++ b/src/components/tooltip/__tests__/__snapshots__/tooltip.test.js.snap
@@ -1,57 +1,43 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`Tooltip all props renders 1`] = `
-<PopoverTrigger
-  content={[Function]}
-  disabled={true}
-  display="inline"
-  popoverProps={
+exports[`Tooltip Button child trigger renders as expected 1`] = `
+<Button
+  block={false}
+  disabled={false}
+  passthroughProps={
     Object {
-      "accessibleTitle": null,
-      "alignment": "left",
-      "backgroundColor": "red",
-      "contentElementAttributes": Object {
-        "id": "tooltip-2",
-        "role": "tooltip",
-      },
-      "hasPointer": true,
-      "hideWhenAnchorIsOffscreen": true,
-      "placement": "top",
-      "themePopover": "py6 px12 round txt-s txt-bold",
+      "aria-describedby": "tooltip-3",
     }
   }
-  receiveFocus={false}
-  renderHiddenContent={true}
-  respondsToClick={true}
-  respondsToFocus={true}
-  respondsToHover={true}
-  trapFocus={false}
-  triggerProps={Object {}}
+  variant="primary"
 >
-  <span>
-    test tooltip
-  </span>
-</PopoverTrigger>
+  trigger
+</Button>
 `;
 
-exports[`Tooltip basic renders 1`] = `
+exports[`Tooltip DOM element child renders as expected 1`] = `
 <PopoverTrigger
-  content="basic"
+  content={
+    <div
+      className="px12 py6 txt-s"
+    >
+      DOM element child
+    </div>
+  }
   disabled={false}
   display="inline-block"
   popoverProps={
     Object {
-      "accessibleTitle": null,
       "alignment": "center",
-      "backgroundColor": "#fff",
-      "contentElementAttributes": Object {
+      "coloring": "light",
+      "hasPointer": true,
+      "hideWhenAnchorIsOffscreen": true,
+      "padded": false,
+      "passthroughProps": Object {
         "id": "tooltip-1",
         "role": "tooltip",
       },
-      "hasPointer": true,
-      "hideWhenAnchorIsOffscreen": true,
       "placement": "top",
-      "themePopover": "round py6 px12 shadow-darken25 txt-s z1",
     }
   }
   receiveFocus={false}
@@ -60,10 +46,27 @@ exports[`Tooltip basic renders 1`] = `
   respondsToFocus={true}
   respondsToHover={true}
   trapFocus={false}
-  triggerProps={Object {}}
 >
-  <span>
-    test tooltip
+  <span
+    aria-describedby="tooltip-1"
+  >
+    trigger
   </span>
 </PopoverTrigger>
 `;
+
+exports[`Tooltip all props trigger renders as expected 1`] = `
+<div
+  aria-describedby="tooltip-4"
+>
+  test tooltip
+</div>
+`;
+
+exports[`Tooltip function child trigger renders as expected 1`] = `
+<CustomThing
+  aria-describedby="tooltip-2"
+>
+  trigger
+</CustomThing>
+`;
diff --git a/src/components/tooltip/__tests__/tooltip-test-cases.js b/src/components/tooltip/__tests__/tooltip-test-cases.js
index f873bb5..1c57fd5 100644
--- a/src/components/tooltip/__tests__/tooltip-test-cases.js
+++ b/src/components/tooltip/__tests__/tooltip-test-cases.js
@@ -1,5 +1,7 @@
+/* eslint-disable react/display-name */
 import React from 'react';
 import Tooltip from '../tooltip';
+import Button from '../../button';
 
 const testCases = {};
 
@@ -23,11 +25,7 @@ function getTooltipList(options = {}) {
           testId={`${options.testId}-default`}
           respondsToClick={options.respondsToClick}
         >
-          {triggerProps => (
-            <button className={buttonClasses} {...triggerProps}>
-              default
-            </button>
-          )}
+          <button className={buttonClasses}>default</button>
         </Tooltip>
       </div>
       <div className={containerClasses}>
@@ -37,11 +35,7 @@ function getTooltipList(options = {}) {
           respondsToClick={options.respondsToClick}
           disabled={true}
         >
-          {triggerProps => (
-            <button className={buttonClasses} {...triggerProps}>
-              disabled
-            </button>
-          )}
+          <button className={buttonClasses}>disabled</button>
         </Tooltip>
       </div>
       <div className={containerClasses}>
@@ -51,11 +45,7 @@ function getTooltipList(options = {}) {
           testId={`${options.testId}-top`}
           respondsToClick={options.respondsToClick}
         >
-          {triggerProps => (
-            <button className={buttonClasses} {...triggerProps}>
-              top
-            </button>
-          )}
+          <button className={buttonClasses}>top</button>
         </Tooltip>
       </div>
       <div className={containerClasses}>
@@ -65,11 +55,7 @@ function getTooltipList(options = {}) {
           testId={`${options.testId}-left`}
           respondsToClick={options.respondsToClick}
         >
-          {triggerProps => (
-            <button className={buttonClasses} {...triggerProps}>
-              left
-            </button>
-          )}
+          <button className={buttonClasses}>left</button>
         </Tooltip>
       </div>
       <div className={containerClasses}>
@@ -79,11 +65,7 @@ function getTooltipList(options = {}) {
           testId={`${options.testId}-right`}
           respondsToClick={options.respondsToClick}
         >
-          {triggerProps => (
-            <button className={buttonClasses} {...triggerProps}>
-              right
-            </button>
-          )}
+          <button className={buttonClasses}>right</button>
         </Tooltip>
       </div>
       <div className={containerClasses}>
@@ -93,23 +75,49 @@ function getTooltipList(options = {}) {
           testId={`${options.testId}-bottom`}
           respondsToClick={options.respondsToClick}
         >
-          {triggerProps => (
-            <button className={buttonClasses} {...triggerProps}>
-              bottom
-            </button>
-          )}
+          <button className={buttonClasses}>bottom</button>
         </Tooltip>
       </div>
     </div>
   );
 }
 
-testCases.basic = {
-  description: 'basic',
+testCases.topFiller = {
+  description:
+    'Filler, so absolutely positioned cases do not overlap the others',
+  element: <div style={{ marginTop: 80 }} />
+};
+
+testCases.domElementChild = {
+  description: 'DOM element child',
   component: Tooltip,
   props: {
-    children: getTooltipChildren,
-    content: 'basic'
+    children: <span>trigger</span>,
+    content: 'DOM element child'
+  }
+};
+
+function CustomThing(props) {
+  return <div {...props} />;
+}
+
+testCases.functionChild = {
+  description: 'function child',
+  component: Tooltip,
+  props: {
+    children: triggerProps => (
+      <CustomThing {...triggerProps}>trigger</CustomThing>
+    ),
+    content: 'function child'
+  }
+};
+
+testCases.buttonChild = {
+  description: 'Button child',
+  component: Tooltip,
+  props: {
+    children: <Button>trigger</Button>,
+    content: 'Button child'
   }
 };
 
@@ -117,15 +125,14 @@ testCases.allProps = {
   description: 'all props',
   component: Tooltip,
   props: {
-    children: getTooltipChildren,
+    children: <div>test tooltip</div>,
     content: getTooltipContent,
     placement: 'top',
     alignment: 'left',
-    disabled: true,
-    backgroundColor: 'red',
+    coloring: 'dark',
     respondsToClick: true,
-    themeTooltip: 'py6 px12 round txt-s txt-bold',
-    display: 'inline'
+    themeTooltip: 'py3 px3 txt-bold txt-l',
+    display: 'inline-block'
   }
 };
 
@@ -182,4 +189,10 @@ testCases.manyPositions = {
   )
 };
 
+testCases.bottomFiller = {
+  description:
+    'Filler, so absolutely positioned cases do not overlap the others',
+  element: <div style={{ marginTop: 80 }} />
+};
+
 export { testCases };
diff --git a/src/components/tooltip/__tests__/tooltip.test.js b/src/components/tooltip/__tests__/tooltip.test.js
index 0abea2f..b1c2a47 100644
--- a/src/components/tooltip/__tests__/tooltip.test.js
+++ b/src/components/tooltip/__tests__/tooltip.test.js
@@ -6,19 +6,45 @@ describe('Tooltip', () => {
   let testCase;
   let wrapper;
 
-  describe(testCases.basic.description, () => {
+  describe(testCases.domElementChild.description, () => {
     beforeEach(() => {
-      testCase = testCases.basic;
+      testCase = testCases.domElementChild;
       wrapper = shallow(
         React.createElement(testCase.component, testCase.props)
       );
     });
 
-    test('renders', () => {
+    test('renders as expected', () => {
       expect(wrapper).toMatchSnapshot();
     });
   });
 
+  describe(testCases.functionChild.description, () => {
+    beforeEach(() => {
+      testCase = testCases.functionChild;
+      wrapper = shallow(
+        React.createElement(testCase.component, testCase.props)
+      );
+    });
+
+    test('trigger renders as expected', () => {
+      expect(wrapper.children()).toMatchSnapshot();
+    });
+  });
+
+  describe(testCases.buttonChild.description, () => {
+    beforeEach(() => {
+      testCase = testCases.buttonChild;
+      wrapper = shallow(
+        React.createElement(testCase.component, testCase.props)
+      );
+    });
+
+    test('trigger renders as expected', () => {
+      expect(wrapper.children()).toMatchSnapshot();
+    });
+  });
+
   describe(testCases.allProps.description, () => {
     beforeEach(() => {
       testCase = testCases.allProps;
@@ -27,8 +53,8 @@ describe('Tooltip', () => {
       );
     });
 
-    test('renders', () => {
-      expect(wrapper).toMatchSnapshot();
+    test('trigger renders as expected', () => {
+      expect(wrapper.children()).toMatchSnapshot();
     });
   });
 });