Repository: uber-web/baseui
Pull request #924 (2019-02-21 17:19:59)

fix(datepicker): improve accessibility, make days focusable instead of the whole month


Patch:
diff --git a/documentation-site/pages/components/table.mdx b/documentation-site/pages/components/table.mdx
index 8ab38d825..ee3cdd614 100644
--- a/documentation-site/pages/components/table.mdx
+++ b/documentation-site/pages/components/table.mdx
@@ -14,6 +14,7 @@ import Cells from 'examples/table/cells.js';
 import FixedWidthColumn from 'examples/table/fixed-width-column.js';
 import VerticalScroll from 'examples/table/vertical-scroll.js';
 import HorizontalScroll from 'examples/table/horizontal-scroll.js';
+import Filter from 'examples/table/filter.js';
 import Virtual from 'examples/table/virtual.js';
 import VirtualHorizontal from 'examples/table/virtual-horizontal-scroll.js';
 
@@ -54,6 +55,10 @@ Display larger amounts of information in a scannable UI.
   <HorizontalScroll />
 </Example>
 
+<Example title="Table with column filtering" path="examples/table/filter.js">
+  <Filter />
+</Example>
+
 ### Virtualized Table Examples
 
 If your product requires infinite scroll functionality rather than pagination, take a look at the
diff --git a/documentation-site/static/examples/checkbox/alignment.js b/documentation-site/static/examples/checkbox/alignment.js
index 1d3f7ca68..dd362d734 100644
--- a/documentation-site/static/examples/checkbox/alignment.js
+++ b/documentation-site/static/examples/checkbox/alignment.js
@@ -3,9 +3,23 @@ import {StatefulCheckbox} from 'baseui/checkbox';
 
 export default () => (
   <React.Fragment>
-    <StatefulCheckbox onChange={console.log} labelPlacement="left">
-      Label on the left
+    <StatefulCheckbox onChange={console.log} labelPlacement="top">
+      Label on the top
     </StatefulCheckbox>
+    <div
+      style={{
+        display: 'flex',
+        justifyContent: 'center',
+        margin: '8px 0px 8px 12px',
+      }}
+    >
+      <StatefulCheckbox onChange={console.log} labelPlacement="left">
+        Label on the left
+      </StatefulCheckbox>
+      <StatefulCheckbox onChange={console.log} labelPlacement="right">
+        Label on the right
+      </StatefulCheckbox>
+    </div>
     <StatefulCheckbox onChange={console.log} labelPlacement="bottom">
       Label on the bottom
     </StatefulCheckbox>
diff --git a/documentation-site/static/examples/table/filter.js b/documentation-site/static/examples/table/filter.js
new file mode 100644
index 000000000..9d11a12cd
--- /dev/null
+++ b/documentation-site/static/examples/table/filter.js
@@ -0,0 +1,110 @@
+import React from 'react';
+import {styled} from 'baseui';
+import {Checkbox} from 'baseui/checkbox';
+import {
+  Filter,
+  StyledTable,
+  StyledHead,
+  StyledHeadCell,
+  StyledBody,
+  StyledRow,
+  StyledCell,
+} from 'baseui/table';
+
+const FilterCheckbox = props => (
+  <Checkbox
+    checked={props.checked}
+    onChange={props.onChange}
+    overrides={{
+      Root: {
+        style: ({$theme}) => ({
+          marginTop: $theme.sizing.scale400,
+          marginBottom: $theme.sizing.scale400,
+        }),
+      },
+      Checkmark: {style: {marginLeft: 0}},
+    }}
+  >
+    {props.children}
+  </Checkbox>
+);
+
+class FilterTable extends React.Component {
+  state = {
+    filters: [],
+  };
+
+  FILTER_FUNCTIONS = [...new Array(10)].map((_, i) => row =>
+    row[0] % (i + 1) === 0,
+  );
+
+  onFilter = index => {
+    const isActive = !!this.state.filters[index];
+
+    const filters = [...this.state.filters];
+    if (isActive) {
+      filters[index] = null;
+    } else {
+      filters[index] = this.FILTER_FUNCTIONS[index];
+    }
+    this.setState({filters});
+  };
+
+  handleReset = () => this.setState({filters: []});
+  handleSelectAll = () => this.setState({filters: this.FILTER_FUNCTIONS});
+
+  render() {
+    const filteredData = this.state.filters
+      .filter(Boolean)
+      .reduce((data, filter) => data.filter(filter), this.props.data);
+
+    return (
+      <StyledTable>
+        <StyledHead>
+          <StyledHeadCell>
+            Number
+            <Filter
+              active={!!this.state.filters.filter(Boolean).length}
+              onReset={this.handleReset}
+              onSelectAll={this.handleSelectAll}
+            >
+              {this.FILTER_FUNCTIONS.map((_, index) => (
+                <FilterCheckbox
+                  key={index}
+                  checked={!!this.state.filters[index]}
+                  onChange={() => this.onFilter(index)}
+                >
+                  {`Divisible by ${index + 1}`}
+                </FilterCheckbox>
+              ))}
+            </Filter>
+          </StyledHeadCell>
+          <StyledHeadCell>Title</StyledHeadCell>
+        </StyledHead>
+
+        <StyledBody>
+          {filteredData.map((row, index) => (
+            <StyledRow key={index}>
+              {row.map((cell, cellIndex) => (
+                <StyledCell key={cellIndex}>{cell}</StyledCell>
+              ))}
+            </StyledRow>
+          ))}
+        </StyledBody>
+      </StyledTable>
+    );
+  }
+}
+
+const Container = styled('div', {
+  height: '500px',
+});
+
+export default () => {
+  const FILTER_DATA = [...new Array(100)].map((_, i) => [i, 'row title']);
+  return (
+    <Container>
+      <FilterTable data={FILTER_DATA} />
+    </Container>
+  );
+};
diff --git a/package.json b/package.json
index 79edfcaba..c105d5ab2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "baseui",
-  "version": "6.1.0",
+  "version": "6.1.1",
   "description": "React implementation of uber design components platform.",
   "keywords": [
     "react",
@@ -130,6 +130,7 @@
   },
   "dependencies": {
     "date-fns": "2.0.0-alpha.27",
+    "focus-trap": "^4.0.2",
     "memoize-one": "^5.0.0",
     "popper.js": "^1.14.3",
     "react-dropzone": "^8.0.0",
diff --git a/src/checkbox/styled-components.js b/src/checkbox/styled-components.js
index b457786e6..d49a03494 100644
--- a/src/checkbox/styled-components.js
+++ b/src/checkbox/styled-components.js
@@ -24,12 +24,26 @@ function getBorderColor(props) {
 
 function getLabelPadding(props) {
   const {$labelPlacement = '', $theme} = props;
+  let paddingDirection;
+  switch ($labelPlacement) {
+    case 'top':
+      paddingDirection = 'Bottom';
+      break;
+    case 'bottom':
+      paddingDirection = 'Top';
+      break;
+    case 'left':
+      paddingDirection = 'Right';
+      break;
+    default:
+    case 'right':
+      paddingDirection = 'Left';
+      break;
+  }
   const {sizing} = $theme;
-  const {scale200} = sizing;
-  const paddingSide =
-    'padding' + $labelPlacement.replace(/^\w/, c => c.toUpperCase());
+  const {scale300} = sizing;
   return {
-    [paddingSide]: scale200,
+    [`padding${paddingDirection}`]: scale300,
   };
 }
 
@@ -168,8 +182,8 @@ export const Checkmark = styled('span', props => {
     backgroundPosition: 'center',
     marginTop: $theme.sizing.scale0,
     marginBottom: $theme.sizing.scale0,
-    marginLeft: $theme.sizing.scale200,
-    marginRight: $theme.sizing.scale200,
+    marginLeft: $theme.sizing.scale0,
+    marginRight: $theme.sizing.scale0,
   };
 });
 
@@ -194,6 +208,7 @@ export const Input = styled('input', {
   overflow: 'hidden',
   margin: 0,
   padding: 0,
+  position: 'absolute',
 });
 
 export const Toggle = styled('div', props => ({
diff --git a/src/datepicker/calendar-header.js b/src/datepicker/calendar-header.js
index 2f9c19328..0404686e9 100644
--- a/src/datepicker/calendar-header.js
+++ b/src/datepicker/calendar-header.js
@@ -41,7 +41,6 @@ export default class CalendarHeader extends React.Component<HeaderPropsT> {
     onMonthChange: () => {},
     onYearChange: () => {},
     overrides: {},
-    setActiveState: () => {},
   };
 
   handleMonthChange = ({value}: {value: Array<{id: number}>}) => {
@@ -60,14 +59,6 @@ export default class CalendarHeader extends React.Component<HeaderPropsT> {
     this.props.onMonthChange({date: subMonths(this.props.date, 1)});
   };
 
-  setActive = () => {
-    this.props.setActiveState(true);
-  };
-
-  setInactive = () => {
-    this.props.setActiveState(false);
-  };
-
   renderPreviousMonthButton = () => {
     const {date, overrides = {}} = this.props;
     const allPrevDaysDisabled = monthDisabledBefore(date, this.props);
@@ -232,8 +223,6 @@ export default class CalendarHeader extends React.Component<HeaderPropsT> {
         // make sure the components functions as expected
         // We can extract the handlers from props overrides
         // and call it along with internal handlers by creating an inline handler
-        // onOpen={this.setInactive}
-        // onClose={this.setActive}
         onChange={this.handleMonthChange}
         // internal and incoming overrides are merged above
         overrides={selectOverrides}
@@ -270,8 +259,6 @@ export default class CalendarHeader extends React.Component<HeaderPropsT> {
         // make sure the components functions as expected
         // We can extract the handlers from props overrides
         // and call it along with internal handlers by creating an inline handler
-        // onOpen={this.setInactive}
-        // onClose={this.setActive}
         onChange={this.handleYearChange}
         // internal and incoming overrides are merged above
         overrides={selectOverrides}
diff --git a/src/datepicker/calendar.js b/src/datepicker/calendar.js
index a494013bb..522b8c8e5 100644
--- a/src/datepicker/calendar.js
+++ b/src/datepicker/calendar.js
@@ -22,6 +22,7 @@ import {
   addDays,
   addMonths,
   getMonth,
+  addWeeks,
   getWeekdayMinInLocale,
   getEffectiveMinDate,
   getEffectiveMaxDate,
@@ -29,17 +30,20 @@ import {
   isAfter,
   isBefore,
   isSameDay,
+  isSameMonth,
+  isSameYear,
+  subDays,
   subWeeks,
   subMonths,
   subYears,
 } from './utils/index.js';
 import {WEEKDAYS} from './constants.js';
 import {getOverrides} from '../helpers/overrides.js';
-import type {CalendarPropsT} from './types.js';
+import type {CalendarPropsT, CalendarInternalState} from './types.js';
 
 export default class Calendar extends React.Component<
   CalendarPropsT,
-  {date: Date},
+  CalendarInternalState,
 > {
   static defaultProps = {
     calFocusedInitially: false,
@@ -61,7 +65,7 @@ export default class Calendar extends React.Component<
     overrides: {},
     peekNextMonth: false,
     value: null,
-    setActiveState: () => {},
+    trapTabbing: false,
   };
 
   root: ?HTMLElement;
@@ -70,8 +74,12 @@ export default class Calendar extends React.Component<
   constructor(props: CalendarPropsT) {
     super(props);
     this.state = {
+      highlightedDate:
+        this.props.highlightedDate ||
+        this.getSingleDate(this.props.value) ||
+        new Date(),
+      isFocused: false,
       date: this.getDateInView(),
-      voiceoverText: '',
     };
   }
 
@@ -126,12 +134,14 @@ export default class Calendar extends React.Component<
   };
 
   handleMonthChange = (date: Date) => {
+    this.setHighlightedDate(date);
     if (this.props.onMonthChange) {
       this.props.onMonthChange({date});
     }
   };
 
   handleYearChange = (date: Date) => {
+    this.setHighlightedDate(date);
     if (this.props.onYearChange) {
       this.props.onYearChange({date});
     }
@@ -170,7 +180,7 @@ export default class Calendar extends React.Component<
       StyledDay,
     );
     return (
-      <MonthHeader {...monthHeaderProps}>
+      <MonthHeader role="presentation" {...monthHeaderProps}>
         {WEEKDAYS.map(offset => {
           const day = addDays(startOfWeek, offset);
           return (
@@ -183,23 +193,121 @@ export default class Calendar extends React.Component<
     );
   };
 
-  setActive = () => {
-    this.props.setActiveState(true, {root: this.root});
+  onKeyDown = (event: KeyboardEvent) => {
+    switch (event.key) {
+      case 'ArrowUp':
+      case 'ArrowDown':
+      case 'ArrowLeft':
+      case 'ArrowRight':
+        this.handleArrowKey(event.key);
+        event.preventDefault();
+        event.stopPropagation();
+        break;
+    }
   };
 
-  setInactive = () => {
-    this.props.setActiveState(false, {
-      calendar: this.calendar,
-      root: this.root,
-    });
+  handleArrowKey = (key: string) => {
+    const {highlightedDate: oldDate} = this.state;
+    let highlightedDate = oldDate;
+    switch (key) {
+      case 'ArrowLeft':
+        // adding `new Date()` as the last option to satisfy Flow
+        highlightedDate = subDays(
+          highlightedDate ? highlightedDate : new Date(),
+          1,
+        );
+        break;
+      case 'ArrowRight':
+        highlightedDate = addDays(
+          // adding `new Date()` as the last option to satisfy Flow
+          highlightedDate ? highlightedDate : new Date(),
+          1,
+        );
+        break;
+      case 'ArrowUp':
+        highlightedDate = subWeeks(
+          // adding `new Date()` as the last option to satisfy Flow
+          highlightedDate ? highlightedDate : new Date(),
+          1,
+        );
+        break;
+      case 'ArrowDown':
+        highlightedDate = addWeeks(
+          // adding `new Date()` as the last option to satisfy Flow
+          highlightedDate ? highlightedDate : new Date(),
+          1,
+        );
+        break;
+    }
+    this.setState({highlightedDate, date: highlightedDate});
   };
 
   focusCalendar = () => {
-    if (this.calendar) {
-      this.calendar.focus();
+    if (!this.state.isFocused) {
+      this.setState({isFocused: true});
+    }
+  };
+
+  blurCalendar = () => {
+    if (__BROWSER__) {
+      const activeElm = document.activeElement;
+      if (this.calendar && !this.calendar.contains(activeElm)) {
+        this.setState({isFocused: false});
+      }
+    }
+  };
+
+  handleTabbing = (event: KeyboardEvent) => {
+    if (__BROWSER__) {
+      if (event.keyCode === 9) {
+        const activeElm = document.activeElement;
+        // need to look for any tabindex >= 0 and ideally for not disabled
+        // focusable by default elements like input, button, etc.
+        const focusable = this.root
+          ? this.root.querySelectorAll('[tabindex="0"]')
+          : null;
+        const length = focusable ? focusable.length : 0;
+        if (event.shiftKey) {
+          if (focusable && activeElm === focusable[0]) {
+            event.preventDefault();
+            focusable[length - 1].focus();
+          }
+        } else {
+          if (focusable && activeElm === focusable[length - 1]) {
+            event.preventDefault();
+            focusable[0].focus();
+          }
+        }
+      }
     }
   };
 
+  onDayMouseOver = (data: {event: Event, date: Date}) => {
+    const {date} = data;
+    this.setState({highlightedDate: date});
+    this.props.onDayMouseOver(data);
+  };
+
+  onDayMouseLeave = (data: {event: Event, date: Date}) => {
+    const {date} = data;
+    this.setHighlightedDate(date);
+    this.props.onDayMouseLeave(data);
+  };
+
+  setHighlightedDate(date: Date) {
+    const {value} = this.props;
+    const selected = this.getSingleDate(value);
+    let nextState;
+    if (selected && isSameMonth(selected, date) && isSameYear(selected, date)) {
+      nextState = {highlightedDate: selected};
+    } else {
+      nextState = {
+        highlightedDate: date,
+      };
+    }
+    this.setState(nextState);
+  }
+
   renderMonths = () => {
     const {overrides = {}} = this.props;
     const monthList = [];
@@ -214,12 +322,10 @@ export default class Calendar extends React.Component<
       monthList.push(
         <CalendarContainer
           key={monthKey}
-          tabIndex={0}
-          onFocus={this.setActive}
-          onBlur={this.setInactive}
           $ref={calendar => {
             this.calendar = calendar;
           }}
+          onKeyDown={this.onKeyDown}
           {...calendarContainerProps}
         >
           {this.renderMonthHeader(monthDate, i)}
@@ -227,16 +333,19 @@ export default class Calendar extends React.Component<
             date={monthDate}
             excludeDates={this.props.excludeDates}
             filterDate={this.props.filterDate}
-            highlightedDate={this.props.highlightedDate}
+            highlightedDate={this.state.highlightedDate}
             includeDates={this.props.includeDates}
+            isFocused={this.state.isFocused}
             isRange={this.props.isRange}
             locale={this.props.locale}
             maxDate={this.props.maxDate}
             minDate={this.props.minDate}
             month={getMonth(this.state.date)}
+            onDayBlur={this.blurCalendar}
+            onDayFocus={this.focusCalendar}
             onDayClick={this.props.onDayClick}
-            onDayMouseOver={this.props.onDayMouseOver}
-            onDayMouseLeave={this.props.onDayMouseLeave}
+            onDayMouseOver={this.onDayMouseOver}
+            onDayMouseLeave={this.onDayMouseLeave}
             onChange={this.props.onChange}
             overrides={overrides}
             value={this.props.value}
@@ -340,6 +449,9 @@ export default class Calendar extends React.Component<
         $ref={root => {
           this.root = root;
         }}
+        role="application"
+        aria-label="calendar"
+        onKeyDown={this.props.trapTabbing ? this.handleTabbing : null}
         {...rootProps}
       >
         {this.renderMonths()}
diff --git a/src/datepicker/datepicker.js b/src/datepicker/datepicker.js
index 2ba568b6a..6a6dee497 100644
--- a/src/datepicker/datepicker.js
+++ b/src/datepicker/datepicker.js
@@ -8,7 +8,6 @@ LICENSE file in the root directory of this source tree.
 import React from 'react';
 import {Input} from '../input/index.js';
 import {Popover, PLACEMENT} from '../popover/index.js';
-import NavigationContainer from './navigation-container.js';
 import Calendar from './calendar.js';
 import {formatDate} from './utils/index.js';
 import {getOverrides} from '../helpers/overrides.js';
@@ -24,8 +23,9 @@ export default class Datepicker extends React.Component<
   },
 > {
   static defaultProps = {
-    'aria-label': null,
+    'aria-label': 'Select a date',
     'aria-labelledby': null,
+    'aria-describedby': 'datepicker--screenreader--message--input',
     disabled: false,
     formatDisplayValue: null,
     formatString: 'YYYY/MM/dd',
@@ -139,39 +139,57 @@ export default class Datepicker extends React.Component<
       Popover,
     );
     return (
-      <PopoverComponent
-        placement={PLACEMENT.bottom}
-        isOpen={this.state.isOpen}
-        onClickOutside={this.close}
-        onEsc={this.handleEsc}
-        content={
-          <NavigationContainer
-            trapTabbing={true}
-            value={this.props.value}
-            calFocusedInitially={this.state.calendarFocused}
-            {...this.props}
-            onChange={this.onChange}
-          >
-            {childrenProps => <Calendar {...childrenProps} />}
-          </NavigationContainer>
-        }
-        {...popoverProps}
-      >
-        <InputComponent
-          aria-disabled={this.props.disabled}
-          aria-label={this.props['aria-label']}
-          aria-labelledby={this.props['aria-labelledby']}
-          aria-required={this.props.required || null}
-          disabled={this.props.disabled}
-          value={this.formatDisplayValue(this.props.value)}
-          onFocus={this.open}
-          onBlur={this.handleInputBlur}
-          onKeyDown={this.handleKeyDown}
-          placeholder={this.props.placeholder}
-          required={this.props.required}
-          {...inputProps}
-        />
-      </PopoverComponent>
+      <React.Fragment>
+        <PopoverComponent
+          placement={PLACEMENT.bottom}
+          isOpen={this.state.isOpen}
+          onClickOutside={this.close}
+          onEsc={this.handleEsc}
+          content={
+            <Calendar
+              calFocusedInitially={this.state.calendarFocused}
+              trapTabbing={true}
+              value={this.props.value}
+              {...this.props}
+              onChange={this.onChange}
+            />
+          }
+          {...popoverProps}
+        >
+          <InputComponent
+            aria-disabled={this.props.disabled}
+            aria-label={this.props['aria-label']}
+            aria-labelledby={this.props['aria-labelledby']}
+            aria-describedby={this.props['aria-describedby']}
+            aria-required={this.props.required || null}
+            disabled={this.props.disabled}
+            value={this.formatDisplayValue(this.props.value)}
+            onFocus={this.open}
+            onBlur={this.handleInputBlur}
+            onKeyDown={this.handleKeyDown}
+            placeholder={this.props.placeholder}
+            required={this.props.required}
+            {...inputProps}
+          />
+        </PopoverComponent>
+        <p
+          id="datepicker--screenreader--message--input"
+          style={{
+            position: 'absolute',
+            width: '1px',
+            height: '1px',
+            margin: '-1px',
+            border: '0px',
+            padding: '0px',
+            overflow: 'hidden',
+            clip: 'react(0px, 0px, 0px, 0px)',
+            clipPath: 'inset(100%)',
+          }}
+        >
+          Press the down arrow key to interact with the calendar and select a
+          date. Press the escape button to close the calendar.
+        </p>
+      </React.Fragment>
     );
   }
 }
diff --git a/src/datepicker/day.js b/src/datepicker/day.js
index d2d92f729..f287affb3 100644
--- a/src/datepicker/day.js
+++ b/src/datepicker/day.js
@@ -35,42 +35,34 @@ export default class Day extends React.Component<DayPropsT, DayStateT> {
     value: null,
   };
 
+  dayElm: ?HTMLElement;
+
   state = {
     isHovered: false,
   };
 
   componentDidMount() {
-    if (this.props.isHighlighted) {
-      if (__BROWSER__) {
-        document.addEventListener('keydown', this.onKeyDown);
+    if (this.dayElm && this.props.isFocused) {
+      if (
+        this.props.isHighlighted ||
+        (!this.props.highlightedDate && this.isSelected())
+      ) {
+        this.dayElm.focus();
       }
     }
   }
 
   componentDidUpdate(prevProps: DayPropsT) {
-    if (
-      this.props.isHighlighted &&
-      this.props.isHighlighted !== prevProps.isHighlighted
-    ) {
-      if (__BROWSER__) {
-        document.addEventListener('keydown', this.onKeyDown);
-      }
-    } else if (
-      !this.props.isHighlighted &&
-      this.props.isHighlighted !== prevProps.isHighlighted
-    ) {
-      if (__BROWSER__) {
-        document.removeEventListener('keydown', this.onKeyDown);
+    if (this.dayElm && this.props.isFocused) {
+      if (
+        this.props.isHighlighted ||
+        (!this.props.highlightedDate && this.isSelected())
+      ) {
+        this.dayElm.focus();
       }
     }
   }
 
-  componentWillUnmount() {
-    if (__BROWSER__) {
-      document.removeEventListener('keydown', this.onKeyDown);
-    }
-  }
-
   onSelect(selectedDate: Date) {
     const {isRange, value} = this.props;
     let date;
@@ -205,6 +197,28 @@ export default class Day extends React.Component<DayPropsT, DayStateT> {
     };
   }
 
+  getAriaLabel(sharedProps: {
+    $disabled: boolean,
+    $isRange: boolean,
+    $selected: boolean,
+    $startDate: boolean,
+  }) {
+    const {date, locale} = this.props;
+    return `${
+      sharedProps.$selected
+        ? sharedProps.$isRange
+          ? sharedProps.$startDate
+            ? 'Selected start date.'
+            : 'Selected end date.'
+          : 'Selected.'
+        : sharedProps.$disabled
+          ? 'Not available.'
+          : 'Choose'
+    } ${formatDate(date, 'EEEE, MMMM do YYYY', locale)}. ${
+      !sharedProps.$disabled ? "It's available." : ''
+    }`;
+  }
+
   render() {
     const {date, peekNextMonth, overrides = {}} = this.props;
     const sharedProps = this.getSharedProps();
@@ -214,15 +228,27 @@ export default class Day extends React.Component<DayPropsT, DayStateT> {
     ) : (
       // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events
       <Day
-        aria-label={formatDate(date, 'EEEE, MMMM do YYYY', this.props.locale)}
-        role="cell"
+        aria-label={this.getAriaLabel(sharedProps)}
+        $ref={dayElm => {
+          this.dayElm = dayElm;
+        }}
+        role="button"
+        tabIndex={
+          this.props.isHighlighted ||
+          (!this.props.highlightedDate && this.isSelected())
+            ? '0'
+            : '-1'
+        }
         {...sharedProps}
         {...dayProps}
         // Adding event handlers after customers overrides in order to
         // make sure the components functions as expected
         // We can extract the handlers from props overrides
         // and call it along with internal handlers by creating an inline handler
+        onBlur={this.props.onBlur}
+        onFocus={this.props.onFocus}
         onClick={this.onClick}
+        onKeyDown={this.onKeyDown}
         onMouseOver={this.onMouseOver}
         onMouseLeave={this.onMouseLeave}
       >
diff --git a/src/datepicker/index.js b/src/datepicker/index.js
index 8b109ade0..bd540cb95 100644
--- a/src/datepicker/index.js
+++ b/src/datepicker/index.js
@@ -6,7 +6,6 @@ LICENSE file in the root directory of this source tree.
 */
 // @flow
 export {default as StatefulContainer} from './stateful-container.js';
-export {default as NavigationContainer} from './navigation-container.js';
 export {default as Unstable_Calendar} from './calendar.js';
 export {default as Unstable_StatefulCalendar} from './stateful-calendar.js';
 export {default as Unstable_Datepicker} from './datepicker.js';
diff --git a/src/datepicker/month.js b/src/datepicker/month.js
index b9a6897da..d51143b08 100644
--- a/src/datepicker/month.js
+++ b/src/datepicker/month.js
@@ -9,7 +9,6 @@ import * as React from 'react';
 import Week from './week.js';
 import {StyledMonth} from './styled-components.js';
 import {
-  formatDate,
   addDays,
   isSameMonth,
   getStartOfWeek,
@@ -64,12 +63,15 @@ export default class CalendarMonth extends React.Component<MonthPropsT> {
           filterDate={this.props.filterDate}
           highlightedDate={this.props.highlightedDate}
           includeDates={this.props.includeDates}
+          isFocused={this.props.isFocused}
           isRange={this.props.isRange}
           key={i}
           locale={this.props.locale}
           minDate={this.props.minDate}
           maxDate={this.props.maxDate}
           month={getMonth(this.props.date)}
+          onDayBlur={this.props.onDayBlur}
+          onDayFocus={this.props.onDayFocus}
           onDayClick={this.props.onDayClick}
           onDayMouseOver={this.props.onDayMouseOver}
           onDayMouseLeave={this.props.onDayMouseLeave}
@@ -91,12 +93,7 @@ export default class CalendarMonth extends React.Component<MonthPropsT> {
     const {overrides = {}} = this.props;
     const [Month, monthProps] = getOverrides(overrides.Month, StyledMonth);
     return (
-      <Month
-        role="grid"
-        aria-multiselectable={this.props.isRange || null}
-        aria-label={`month-${formatDate(this.props.date, 'YYYY-MM')}`}
-        {...monthProps}
-      >
+      <Month aria-multiselectable={this.props.isRange || null} {...monthProps}>
         {this.renderWeeks()}
       </Month>
     );
diff --git a/src/datepicker/navigation-container.js b/src/datepicker/navigation-container.js
deleted file mode 100644
index a11d8de3d..000000000
--- a/src/datepicker/navigation-container.js
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
-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 {STATE_CHANGE_TYPE} from './constants.js';
-import {
-  addDays,
-  isSameMonth,
-  isSameYear,
-  subDays,
-  addWeeks,
-  subWeeks,
-} from './utils/index.js';
-import type {
-  NavigationContainerStateT,
-  NavigationContainerPropsT,
-  StateChangeTypeT,
-  NavigationContainerStateReducerT,
-} from './types.js';
-
-const defaultStateReducer: NavigationContainerStateReducerT = (
-  type,
-  nextState,
-) => nextState;
-
-class NavigationContainer extends React.Component<
-  NavigationContainerPropsT,
-  NavigationContainerStateT,
-> {
-  static defaultProps = {
-    stateReducer: defaultStateReducer,
-    onDayMouseOver: () => {},
-    onDayMouseLeave: () => {},
-    onChange: () => {},
-    trapTabbing: false,
-  };
-
-  root: ?HTMLElement;
-
-  state = {
-    highlightedDate:
-      this.props.highlightedDate ||
-      this.getSingleDate(this.props.value) ||
-      new Date(),
-    isActive: false,
-    lastHighlightedDate:
-      this.props.highlightedDate ||
-      this.getSingleDate(this.props.value) ||
-      new Date(),
-  };
-
-  componentDidMount() {
-    if (__BROWSER__) {
-      if (this.props.trapTabbing) {
-        document.addEventListener('keydown', this.handleTabbing);
-      }
-    }
-  }
-
-  componentWillUnmount() {
-    if (__BROWSER__) {
-      if (this.props.trapTabbing) {
-        document.removeEventListener('keydown', this.handleTabbing);
-      }
-    }
-  }
-
-  setActiveState = (isActive: boolean, {root}: {root: ?HTMLElement}) => {
-    if (__BROWSER__) {
-      this.root = root;
-      const method = isActive ? 'addEventListener' : 'removeEventListener';
-      // $FlowFixMe
-      document[method]('keydown', this.onKeyDown);
-      this.internalSetState(null, {isActive});
-    }
-  };
-
-  getSingleDate(value: ?Date | Array<Date>): ?Date {
-    if (Array.isArray(value)) {
-      return value[0] || null;
-    } else {
-      return value;
-    }
-  }
-
-  onChange = (data: {date: ?Date | Array<Date>}) => {
-    this.props.onChange(data);
-  };
-
-  onMonthChange = (data: {date: Date}) => {
-    const {date} = data;
-    this.setHighlightedDate(date);
-    if (this.props.onMonthChange) {
-      this.props.onMonthChange(data);
-    }
-  };
-
-  onYearChange = (data: {date: Date}) => {
-    const {date} = data;
-    this.setHighlightedDate(date);
-    if (this.props.onYearChange) {
-      this.props.onYearChange(data);
-    }
-  };
-
-  onKeyDown = (event: KeyboardEvent) => {
-    switch (event.key) {
-      case 'ArrowUp':
-      case 'ArrowDown':
-      case 'ArrowLeft':
-      case 'ArrowRight':
-        this.handleArrowKey(event.key);
-        event.preventDefault();
-        event.stopPropagation();
-        break;
-    }
-  };
-
-  handleTabbing = (event: KeyboardEvent) => {
-    if (__BROWSER__) {
-      if (event.keyCode === 9) {
-        const activeElm = document.activeElement;
-        // need to look for any tabindex >= 0 and ideally for not disabled
-        // focusable by default elements like input, button, etc.
-        const focusable = this.root
-          ? this.root.querySelectorAll('[tabindex="0"]')
-          : null;
-        const length = focusable ? focusable.length : 0;
-        if (event.shiftKey) {
-          if (focusable && activeElm === focusable[0]) {
-            event.preventDefault();
-            focusable[length - 1].focus();
-          }
-        } else {
-          if (focusable && activeElm === focusable[length - 1]) {
-            event.preventDefault();
-            focusable[0].focus();
-          }
-        }
-      }
-    }
-  };
-
-  onDayMouseOver = (data: {event: Event, date: Date}) => {
-    const {date} = data;
-    const {stateReducer} = this.props;
-    this.setState(prevState =>
-      stateReducer(
-        STATE_CHANGE_TYPE.mouseOver,
-        {highlightedDate: date},
-        prevState,
-      ),
-    );
-    this.props.onDayMouseOver(data);
-  };
-
-  onDayMouseLeave = (data: {event: Event, date: Date}) => {
-    const {date} = data;
-    this.setHighlightedDate(date);
-    this.props.onDayMouseLeave(data);
-  };
-
-  setHighlightedDate(date: Date) {
-    const {stateReducer, value} = this.props;
-    const selected = this.getSingleDate(value);
-    let nextState;
-    if (selected && isSameMonth(selected, date) && isSameYear(selected, date)) {
-      nextState = {highlightedDate: selected};
-    } else {
-      nextState = {
-        lastHighlightedDate: date,
-        highlightedDate: null,
-      };
-    }
-    this.setState(prevState =>
-      stateReducer(STATE_CHANGE_TYPE.mouseLeave, nextState, prevState),
-    );
-  }
-
-  handleArrowKey(key: string) {
-    const {highlightedDate: oldDate, lastHighlightedDate} = this.state;
-    let highlightedDate = oldDate ? oldDate : null;
-    let stateChangeType = null;
-    switch (key) {
-      case 'ArrowLeft':
-        // adding `new Date()` as the last option to satisfy Fow
-        highlightedDate = subDays(
-          highlightedDate ? highlightedDate : lastHighlightedDate || new Date(),
-          1,
-        );
-        stateChangeType = STATE_CHANGE_TYPE.moveLeft;
-        break;
-      case 'ArrowRight':
-        highlightedDate = addDays(
-          // adding `new Date()` as the last option to satisfy Fow
-          highlightedDate ? highlightedDate : lastHighlightedDate || new Date(),
-          1,
-        );
-        stateChangeType = STATE_CHANGE_TYPE.moveRight;
-        break;
-      case 'ArrowUp':
-        highlightedDate = subWeeks(
-          // adding `new Date()` as the last option to satisfy Fow
-          highlightedDate ? highlightedDate : lastHighlightedDate || new Date(),
-          1,
-        );
-        stateChangeType = STATE_CHANGE_TYPE.moveUp;
-        break;
-      case 'ArrowDown':
-        highlightedDate = addWeeks(
-          // adding `new Date()` as the last option to satisfy Fow
-          highlightedDate ? highlightedDate : lastHighlightedDate || new Date(),
-          1,
-        );
-        stateChangeType = STATE_CHANGE_TYPE.moveDown;
-        break;
-    }
-    this.internalSetState(stateChangeType, {highlightedDate});
-  }
-
-  internalSetState(type: StateChangeTypeT, changes: NavigationContainerStateT) {
-    const {stateReducer} = this.props;
-    this.setState(prevState => stateReducer(type, changes, prevState));
-  }
-
-  render() {
-    const {children, stateReducer, ...rest} = this.props;
-    // $FlowFixMe
-    return this.props.children({
-      ...rest,
-      highlightedDate: this.state.highlightedDate,
-      onDayMouseOver: this.onDayMouseOver,
-      onDayMouseLeave: this.onDayMouseLeave,
-      onChange: this.onChange,
-      onMonthChange: this.onMonthChange,
-      onYearChange: this.onYearChange,
-      setActiveState: this.setActiveState,
-    });
-  }
-}
-
-export default NavigationContainer;
diff --git a/src/datepicker/stateful-calendar.js b/src/datepicker/stateful-calendar.js
index edcfa79d2..d4208f1dd 100644
--- a/src/datepicker/stateful-calendar.js
+++ b/src/datepicker/stateful-calendar.js
@@ -7,18 +7,13 @@ LICENSE file in the root directory of this source tree.
 // @flow
 import * as React from 'react';
 import StatefulContainer from './stateful-container.js';
-import NavigationContainer from './navigation-container.js';
 import Calendar from './calendar.js';
 import type {CalendarPropsT, StatefulDatepickerPropsT} from './types.js';
 
 function StatefulComponent(props: StatefulDatepickerPropsT<CalendarPropsT>) {
   return (
     <StatefulContainer {...props}>
-      {extendedProps => (
-        <NavigationContainer {...extendedProps}>
-          {componentProps => <Calendar {...componentProps} />}
-        </NavigationContainer>
-      )}
+      {extendedProps => <Calendar {...extendedProps} />}
     </StatefulContainer>
   );
 }
diff --git a/src/datepicker/types.js b/src/datepicker/types.js
index 8a55d17a9..0bf476da0 100644
--- a/src/datepicker/types.js
+++ b/src/datepicker/types.js
@@ -48,10 +48,13 @@ export type DayPropsT = {
   includeDates: ?Array<Date>,
   isHighlighted: boolean,
   isRange: boolean,
+  isFocused: boolean,
   locale: ?LocaleT,
   maxDate: ?Date,
   minDate: ?Date,
   month: ?number,
+  onBlur: ({event: Event, date: Date}) => mixed,
+  onFocus: ({event: Event, date: Date}) => mixed,
   onSelect: ({date: ?Date | Array<Date>}) => mixed,
   onClick: ({event: Event, date: Date}) => mixed,
   onMouseOver: ({event: Event, date: Date}) => mixed,
@@ -72,12 +75,15 @@ export type WeekPropsT = {
   // highlighted while keyboard navigating or hovered
   highlightedDate: ?Date,
   includeDates: ?Array<Date>,
+  isFocused: boolean,
   isRange: boolean,
   locale: ?LocaleT,
   maxDate: ?Date,
   minDate: ?Date,
   month: ?number,
+  onDayBlur: ({date: Date, event: Event}) => mixed,
   onDayClick: ({date: Date, event: Event}) => mixed,
+  onDayFocus: ({date: Date, event: Event}) => mixed,
   onDayMouseOver: ({date: Date, event: Event}) => mixed,
   onDayMouseLeave: ({date: Date, event: Event}) => mixed,
   onChange: onChangeT,
@@ -88,6 +94,12 @@ export type WeekPropsT = {
 
 export type MonthPropsT = WeekPropsT;
 
+export type CalendarInternalState = {
+  highlightedDate: Date,
+  isFocused: boolean,
+  date: Date,
+};
+
 export type CalendarPropsT = {
   /** Defines if the calendar is set to be focused on an initial render. */
   calFocusedInitially: boolean,
@@ -126,10 +138,10 @@ export type CalendarPropsT = {
   overrides?: DatepickerOverridesT<{}>,
   /** Defines if dates outside of the range of the current month are displayed. */
   peekNextMonth: boolean,
+  /** Defines if tabbing inside the calendar is circled within it. */
+  trapTabbing: boolean,
   /** Currently selected date. */
   value: ?Date | Array<Date>,
-  /** A helper handler for disabling a keyboard navigation and keyboard selection through the calendar dates while navigation through the month or year select controls. */
-  setActiveState: (boolean, ?{root: ?HTMLElement}) => mixed,
 };
 
 export type HeaderPropsT = CalendarPropsT & {
@@ -139,6 +151,7 @@ export type HeaderPropsT = CalendarPropsT & {
 export type DatepickerPropsT = CalendarPropsT & {
   'aria-label': ?string,
   'aria-labelledby': ?string,
+  'aria-describedby': ?string,
   disabled: boolean,
   placeholder: string,
   required: boolean,
diff --git a/src/datepicker/week.js b/src/datepicker/week.js
index b43a7d4a7..b2ef72100 100644
--- a/src/datepicker/week.js
+++ b/src/datepicker/week.js
@@ -47,6 +47,7 @@ export default class Week extends React.Component<WeekPropsT> {
             highlightedDate={this.props.highlightedDate}
             isHighlighted={isSameDay(day, this.props.highlightedDate)}
             includeDates={this.props.includeDates}
+            isFocused={this.props.isFocused}
             isRange={this.props.isRange}
             key={offset}
             locale={this.props.locale}
@@ -54,6 +55,8 @@ export default class Week extends React.Component<WeekPropsT> {
             maxDate={this.props.maxDate}
             month={this.props.month}
             onSelect={this.props.onChange}
+            onBlur={this.props.onDayBlur}
+            onFocus={this.props.onDayFocus}
             onClick={this.props.onDayClick}
             onMouseOver={this.props.onDayMouseOver}
             onMouseLeave={this.props.onDayMouseLeave}
@@ -69,10 +72,6 @@ export default class Week extends React.Component<WeekPropsT> {
   render() {
     const {overrides = {}} = this.props;
     const [Week, weekProps] = getOverrides(overrides.Week, StyledWeek);
-    return (
-      <Week role="row" {...weekProps}>
-        {this.renderDays()}
-      </Week>
-    );
+    return <Week {...weekProps}>{this.renderDays()}</Week>;
   }
 }
diff --git a/src/file-uploader/file-uploader.js b/src/file-uploader/file-uploader.js
index d13de062b..c7b7fe652 100644
--- a/src/file-uploader/file-uploader.js
+++ b/src/file-uploader/file-uploader.js
@@ -31,40 +31,26 @@ function prependStyleProps(styleProps) {
   }, {});
 }
 
-function makeOverride(Override, Source) {
-  const OverriddenComponent = props => {
-    const [Overridden, overrideProps] = getOverrides(Override, Source);
-    return <Overridden {...props} {...overrideProps} />;
-  };
-
-  OverriddenComponent.displayName = Source.displayName;
-  return OverriddenComponent;
-}
-
-function makeOverrides(overrides = {}) {
-  return {
-    Root: makeOverride(overrides.Root, StyledRoot),
-    FileDragAndDrop: makeOverride(
-      overrides.FileDragAndDrop,
-      StyledFileDragAndDrop,
-    ),
-    ContentMessage: makeOverride(
-      overrides.ContentMessage,
-      StyledContentMessage,
-    ),
-    ErrorMessage: makeOverride(overrides.ErrorMessage, StyledErrorMessage),
-    HiddenInput: makeOverride(overrides.HiddenInput, StyledHiddenInput),
-  };
-}
-
 function FileUploader(props: PropsT) {
-  const {
-    Root,
-    FileDragAndDrop,
-    ContentMessage,
-    ErrorMessage,
-    HiddenInput,
-  } = makeOverrides(props.overrides);
+  const {overrides = {}} = props;
+
+  const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot);
+  const [FileDragAndDrop, fileDragAndDropProps] = getOverrides(
+    overrides.FileDragAndDrop,
+    StyledFileDragAndDrop,
+  );
+  const [ContentMessage, contentMessageProps] = getOverrides(
+    overrides.ContentMessage,
+    StyledContentMessage,
+  );
+  const [ErrorMessage, errorMessageProps] = getOverrides(
+    overrides.ErrorMessage,
+    StyledErrorMessage,
+  );
+  const [HiddenInput, hiddenInputProps] = getOverrides(
+    overrides.HiddenInput,
+    StyledHiddenInput,
+  );
 
   const afterFileDrop = !!(
     props.progressAmount ||
@@ -84,17 +70,26 @@ function FileUploader(props: PropsT) {
         });
 
         return (
-          <Root {...prefixedStyledProps}>
+          <Root {...prefixedStyledProps} {...rootProps}>
             <FileDragAndDrop
               {...getRootProps({refKey: '$ref'})}
               {...prefixedStyledProps}
+              {...fileDragAndDropProps}
             >
               {!afterFileDrop && (
                 <React.Fragment>
-                  <ContentMessage {...prefixedStyledProps}>
+                  <ContentMessage
+                    {...prefixedStyledProps}
+                    {...contentMessageProps}
+                  >
                     Drop files here to upload
                   </ContentMessage>
-                  <ContentMessage {...prefixedStyledProps}>or</ContentMessage>
+                  <ContentMessage
+                    {...prefixedStyledProps}
+                    {...contentMessageProps}
+                  >
+                    or
+                  </ContentMessage>
 
                   <Button
                     aria-controls="fileupload"
@@ -142,11 +137,17 @@ function FileUploader(props: PropsT) {
                   )}
                   {(props.errorMessage || props.progressMessage) &&
                   props.errorMessage ? (
-                    <ErrorMessage {...prefixedStyledProps}>
+                    <ErrorMessage
+                      {...prefixedStyledProps}
+                      {...errorMessageProps}
+                    >
                       {props.errorMessage}
                     </ErrorMessage>
                   ) : (
-                    <ContentMessage {...prefixedStyledProps}>
+                    <ContentMessage
+                      {...prefixedStyledProps}
+                      {...contentMessageProps}
+                    >
                       {props.progressMessage}
                     </ContentMessage>
                   )}
@@ -186,6 +187,7 @@ function FileUploader(props: PropsT) {
             <HiddenInput
               {...getInputProps({refKey: '$ref'})}
               {...prefixedStyledProps}
+              {...hiddenInputProps}
             />
           </Root>
         );
diff --git a/src/input/base-input.js b/src/input/base-input.js
index 9dce68e88..6ac80ac37 100644
--- a/src/input/base-input.js
+++ b/src/input/base-input.js
@@ -27,6 +27,7 @@ class BaseInput<T: EventTarget> extends React.Component<
   static defaultProps = {
     'aria-label': null,
     'aria-labelledby': null,
+    'aria-describedby': null,
     adjoined: ADJOINED.none,
     autoFocus: false,
     disabled: false,
@@ -90,6 +91,7 @@ class BaseInput<T: EventTarget> extends React.Component<
       $ref: $ref || inputRef,
       'aria-label': this.props['aria-label'],
       'aria-labelledby': this.props['aria-labelledby'],
+      'aria-describedby': this.props['aria-describedby'],
       'aria-invalid': !!error,
       'aria-required': required,
       disabled,
diff --git a/src/input/types.js b/src/input/types.js
index bb1616679..74db720dd 100644
--- a/src/input/types.js
+++ b/src/input/types.js
@@ -68,6 +68,8 @@ export type BaseInputPropsT<T> = {
   'aria-label': ?string,
   /** Sets aria-labelledby attribute. */
   'aria-labelledby': ?string,
+  /** Sets aria-describedby attribute. */
+  'aria-describedby': ?string,
   /** Defines styles for inputs that are grouped with other controls. */
   adjoined: AdjoinedT,
   /** If true the input will be focused on the first mount. */
diff --git a/src/radio/styled-components.js b/src/radio/styled-components.js
index 30c23ec91..cc9799bc6 100644
--- a/src/radio/styled-components.js
+++ b/src/radio/styled-components.js
@@ -19,13 +19,26 @@ function getBorderColor(props) {
 
 function getLabelPadding(props) {
   const {$labelPlacement = '', $theme} = props;
-  const {
-    sizing: {scale200},
-  } = $theme;
-  const paddingSide =
-    'padding' + $labelPlacement.replace(/^\w/, c => c.toUpperCase());
+  let paddingDirection;
+  switch ($labelPlacement) {
+    case 'top':
+      paddingDirection = 'Bottom';
+      break;
+    case 'bottom':
+      paddingDirection = 'Top';
+      break;
+    case 'left':
+      paddingDirection = 'Right';
+      break;
+    default:
+    case 'right':
+      paddingDirection = 'Left';
+      break;
+  }
+  const {sizing} = $theme;
+  const {scale300} = sizing;
   return {
-    [paddingSide]: scale200,
+    [`padding${paddingDirection}`]: scale300,
   };
 }
 
@@ -46,7 +59,8 @@ export const RadioGroupRoot = styled('div', props => {
 });
 
 export const Root = styled('label', props => {
-  const {$disabled, $labelPlacement} = props;
+  const {$disabled, $labelPlacement, $theme} = props;
+  const {sizing} = $theme;
   return {
     flexDirection:
       $labelPlacement === 'top' || $labelPlacement === 'bottom'
@@ -55,6 +69,8 @@ export const Root = styled('label', props => {
     display: 'flex',
     alignItems: 'center',
     cursor: $disabled ? 'not-allowed' : 'pointer',
+    marginTop: sizing.scale200,
+    marginBottom: sizing.scale200,
   };
 });
 
@@ -95,10 +111,10 @@ export const RadioMarkOuter = styled('div', props => {
     display: 'flex',
     height: sizing.scale700,
     justifyContent: 'center',
-    marginTop: sizing.scale200,
-    marginRight: sizing.scale200,
-    marginBottom: sizing.scale200,
-    marginLeft: sizing.scale200,
+    marginTop: sizing.scale0,
+    marginRight: sizing.scale0,
+    marginBottom: sizing.scale0,
+    marginLeft: sizing.scale0,
     verticalAlign: 'middle',
     width: sizing.scale700,
     flexShrink: 0,
@@ -113,7 +129,7 @@ export const Label = styled('div', props => {
     verticalAlign: 'middle',
     ...getLabelPadding(props),
     color: getLabelColor(props),
-    ...typography.font400,
+    ...typography.font450,
   };
 });
 
@@ -124,4 +140,5 @@ export const Input = styled('input', {
   overflow: 'hidden',
   margin: 0,
   padding: 0,
+  position: 'absolute',
 });
diff --git a/src/table/filter.js b/src/table/filter.js
new file mode 100644
index 000000000..4fabc1aab
--- /dev/null
+++ b/src/table/filter.js
@@ -0,0 +1,123 @@
+/*
+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 {Button, KIND, SIZE} from '../button/index.js';
+import {getOverrides} from '../helpers/overrides.js';
+import FilterIcon from '../icon/filter.js';
+import {StatefulPopover, PLACEMENT} from '../popover/index.js';
+
+import {
+  StyledFilterButton,
+  StyledFilterContent,
+  StyledFilterHeading,
+  StyledFilterFooter,
+} from './styled-components.js';
+import TrapFocus from './trap-focus.js';
+import type {FilterProps} from './types.js';
+
+export default function Filter(props: FilterProps) {
+  const {onSelectAll = () => {}, onReset = () => {}, overrides = {}} = props;
+
+  const [MenuButton, menuButtonProps] = getOverrides(
+    overrides.MenuButton,
+    StyledFilterButton,
+  );
+
+  const [Content, contentProps] = getOverrides(
+    overrides.Content,
+    StyledFilterContent,
+  );
+
+  const [Heading, headingProps] = getOverrides(
+    overrides.Heading,
+    StyledFilterHeading,
+  );
+
+  const [Footer, footerProps] = getOverrides(
+    overrides.Footer,
+    StyledFilterFooter,
+  );
+
+  function getIconColor(theme) {
+    if (props.disabled) {
+      return theme.colors.mono500;
+    }
+
+    if (props.active) {
+      return theme.colors.primary;
+    }
+
+    return theme.colors.mono600;
+  }
+
+  function getIconHoverColor(theme) {
+    if (props.disabled || props.active) {
+      return null;
+    }
+
+    return theme.colors.black;
+  }
+
+  return (
+    <StatefulPopover
+      placement={PLACEMENT.bottom}
+      stateReducer={(_, nextState) => {
+        if (props.disabled) {
+          return {...nextState, isOpen: false};
+        }
+        return nextState;
+      }}
+      content={
+        <TrapFocus>
+          <Heading {...headingProps}>Filter Column</Heading>
+          <Content {...contentProps}>{props.children}</Content>
+          <Footer {...footerProps}>
+            <Button
+              kind={KIND.minimal}
+              size={SIZE.compact}
+              onClick={() => {
+                onSelectAll();
+              }}
+            >
+              Select All
+            </Button>
+
+            <Button
+              kind={KIND.minimal}
+              size={SIZE.compact}
+              onClick={() => {
+                onReset();
+              }}
+            >
+              Reset
+            </Button>
+          </Footer>
+        </TrapFocus>
+      }
+    >
+      <MenuButton {...menuButtonProps}>
+        <FilterIcon
+          overrides={{
+            Svg: {
+              style: ({$theme}) => ({
+                color: getIconColor($theme),
+                ':hover': {
+                  color: getIconHoverColor($theme),
+                  cursor: props.disabled ? null : 'pointer',
+                },
+              }),
+            },
+          }}
+          size={18}
+        />
+      </MenuButton>
+    </StatefulPopover>
+  );
+}
diff --git a/src/table/index.js b/src/table/index.js
index 3a3d6f232..0d2e454d9 100644
--- a/src/table/index.js
+++ b/src/table/index.js
@@ -6,9 +6,14 @@ LICENSE file in the root directory of this source tree.
 */
 // @flow
 export {default as Table} from './table.js';
+export {default as Filter} from './filter.js';
 // Styled elements
 export {
   StyledTable,
+  StyledFilterButton,
+  StyledFilterContent,
+  StyledFilterHeading,
+  StyledFilterFooter,
   StyledHead,
   StyledHeadCell,
   StyledBody,
diff --git a/src/table/styled-components.js b/src/table/styled-components.js
index d95d52a38..54d9eb599 100644
--- a/src/table/styled-components.js
+++ b/src/table/styled-components.js
@@ -45,8 +45,10 @@ export const StyledHeadCell = styled('div', ({$theme}: SharedStylePropsT) => {
     borderTop: 'none',
     borderBottom: 'none',
     borderLeft: 'none',
+    display: 'flex',
+    justifyContent: 'space-between',
     paddingTop: $theme.sizing.scale500,
-    paddingRight: 0,
+    paddingRight: $theme.sizing.scale600,
     paddingBottom: $theme.sizing.scale500,
     paddingLeft: $theme.sizing.scale600,
     width: '100%',
@@ -81,6 +83,41 @@ export const StyledCell = styled('div', ({$theme}: SharedStylePropsT) => {
   };
 });
 
+export const StyledFilterButton = styled('button', {
+  border: 'none',
+  padding: 'none',
+});
+
+export const StyledFilterContent = styled('div', ({$theme}) => ({
+  ...$theme.borders.border300,
+  borderRight: 'none',
+  borderLeft: 'none',
+  maxHeight: '196px',
+  paddingRight: $theme.sizing.scale600,
+  paddingLeft: $theme.sizing.scale600,
+  overflow: 'auto',
+}));
+
+export const StyledFilterHeading = styled('div', ({$theme}) => ({
+  ...$theme.typography.font350,
+  color: $theme.colors.mono700,
+  paddingTop: $theme.sizing.scale500,
+  paddingRight: $theme.sizing.scale600,
+  paddingBottom: $theme.sizing.scale500,
+  paddingLeft: $theme.sizing.scale600,
+}));
+
+export const StyledFilterFooter = styled('div', ({$theme}) => ({
+  backgroundColor: $theme.colors.mono200,
+  paddingTop: $theme.sizing.scale300,
+  paddingRight: $theme.sizing.scale100,
+  paddingBottom: $theme.sizing.scale300,
+  paddingLeft: $theme.sizing.scale100,
+  display: 'flex',
+  justifyContent: 'space-between',
+  width: '216px',
+}));
+
 export const StyledAction = styled('button', ({$theme}) => {
   return {
     border: 'none',
diff --git a/src/table/trap-focus.js b/src/table/trap-focus.js
new file mode 100644
index 000000000..4b9d122b7
--- /dev/null
+++ b/src/table/trap-focus.js
@@ -0,0 +1,45 @@
+/*
+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.
+*/
+// references https://github.com/davidtheclark/focus-trap-react
+// @flow
+
+import * as React from 'react';
+import createFocusTrap from 'focus-trap';
+
+type Props = {
+  children: React.Node,
+};
+
+class TrapFocus extends React.Component<Props, {}> {
+  focusTrapElement = (React.createRef(): {current: HTMLDivElement | null});
+  previouslyFocusedElement: ?HTMLElement = null;
+  // eslint-disable-next-line flowtype/no-weak-types
+  focusTrap: ?{deactivate: Function, activate: Function} = null;
+
+  componentDidMount() {
+    if (__BROWSER__) {
+      if (typeof document !== 'undefined') {
+        this.previouslyFocusedElement = document.activeElement;
+      }
+      this.focusTrap = createFocusTrap(this.focusTrapElement.current);
+      this.focusTrap.activate();
+    }
+  }
+
+  componentWillUnmount() {
+    if (__BROWSER__) {
+      this.focusTrap && this.focusTrap.deactivate();
+      this.previouslyFocusedElement && this.previouslyFocusedElement.focus();
+    }
+  }
+
+  render() {
+    return <div ref={this.focusTrapElement}>{this.props.children}</div>;
+  }
+}
+
+export default TrapFocus;
diff --git a/src/table/types.js b/src/table/types.js
index 53eeae7e9..d48db3a5e 100644
--- a/src/table/types.js
+++ b/src/table/types.js
@@ -8,15 +8,41 @@ LICENSE file in the root directory of this source tree.
 
 import * as React from 'react';
 
+import type {OverrideT} from '../helpers/overrides.js';
 import type {ThemeT} from '../styles/types.js';
 
 export type TablePropsT = {|
+  /** Table columns. Data passed to each header cell. */
   columns: Array<string | React.Node>,
+  /** Table rows. Data passed to each row and cell */
   data: Array<Array<React.Node>>,
+  /** Table width fills this provided value. */
   horizontalScrollWidth?: string,
+  /** Renders the table in a loading state. Not implemented, yet. */
   isLoading?: boolean,
 |};
 
+export type FilterProps = {|
+  /** Displays the filter icon in an active state. */
+  active?: boolean,
+  /** Arbitrary content to display in the filter popover. You will likely want to add checkboxes
+   * or similar inputs to control filtering.
+   */
+  children: React.Node,
+  /** Disables the icon click action. Filter menu does not open when clicked. */
+  disabled?: boolean,
+  /** Callback for when the 'reset' button is clicked. */
+  onReset?: () => mixed,
+  /** Callback for when the 'select all' button is clicked. */
+  onSelectAll?: () => mixed,
+  overrides?: {
+    MenuButton: OverrideT<*>,
+    Content: OverrideT<*>,
+    Heading: OverrideT<*>,
+    Footer: OverrideT<*>,
+  },
+|};
+
 export type SharedStylePropsT = {|
   $theme: ThemeT,
 |};
diff --git a/src/tag/styled-components.js b/src/tag/styled-components.js
index 475691e85..5b106b325 100644
--- a/src/tag/styled-components.js
+++ b/src/tag/styled-components.js
@@ -71,15 +71,13 @@ export const Text = styled('span', props => {
 });
 
 export const Root = styled('span', props => {
-  const {$color, $disabled, $kind, $theme} = props;
+  const {$color, $disabled, $kind, $theme, $closeable} = props;
   const {
     sizing: {scale800, scale100, scale500},
     typography: {font250},
   } = $theme;
   const color = getColor($theme, $kind, $color);
-  const paddingRightIfNotCloseable = props.$closeable
-    ? {}
-    : {paddingRight: scale500};
+  const paddingRightIfNotCloseable = $closeable ? {} : {paddingRight: scale500};
 
   return {
     ...font250,
@@ -105,8 +103,11 @@ export const Root = styled('span', props => {
       : '0px',
     cursor: $disabled ? 'not-allowed' : 'auto',
     backgroundColor: getBackgroundColor(props),
-    ':hover': {
-      backgroundColor: getHoverBackgroundColor(props),
-    },
+    ':hover':
+      $closeable === false
+        ? {}
+        : {
+            backgroundColor: getHoverBackgroundColor(props),
+          },
   };
 });
diff --git a/yarn.lock b/yarn.lock
index 666d3b243..962f7b382 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6765,6 +6765,14 @@ flush-write-stream@^1.0.0:
     inherits "^2.0.1"
     readable-stream "^2.0.4"
 
+focus-trap@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-4.0.2.tgz#4ee2b96547c9ea0e4252a2d4b2cca68944194663"
+  integrity sha512-HtLjfAK7Hp2qbBtLS6wEznID1mPT+48ZnP2nkHzgjpL4kroYHg0CdqJ5cTXk+UO5znAxF5fRUkhdyfgrhh8Lzw==
+  dependencies:
+    tabbable "^3.1.2"
+    xtend "^4.0.1"
+
 for-in@^0.1.3:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
@@ -13301,6 +13309,11 @@ symbol.prototype.description@^1.0.0:
   dependencies:
     has-symbols "^1.0.0"
 
+tabbable@^3.1.2:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-3.1.2.tgz#f2d16cccd01f400e38635c7181adfe0ad965a4a2"
+  integrity sha512-wjB6puVXTYO0BSFtCmWQubA/KIn7Xvajw0x0l6eJUudMG/EAiJvIUnyNX6xO4NpGrJ16lbD0eUseB9WxW0vlpQ==
+
 table@^4.0.3:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc"


Test patch:
diff --git a/src/checkbox/__tests__/__snapshots__/styled-components.test.js.snap b/src/checkbox/__tests__/__snapshots__/styled-components.test.js.snap
index 09bbff943..b96dfdd0c 100644
--- a/src/checkbox/__tests__/__snapshots__/styled-components.test.js.snap
+++ b/src/checkbox/__tests__/__snapshots__/styled-components.test.js.snap
@@ -16,8 +16,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -43,8 +43,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -70,8 +70,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -97,8 +97,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -124,8 +124,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -151,8 +151,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -178,8 +178,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -205,8 +205,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -232,8 +232,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -259,8 +259,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -286,8 +286,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -313,8 +313,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -340,8 +340,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -367,8 +367,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -394,8 +394,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -421,8 +421,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -448,8 +448,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -475,8 +475,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -502,8 +502,8 @@ Object {
   "height": "$theme.sizing.scale700",
   "left": "4px",
   "marginBottom": "$theme.sizing.scale0",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
   "marginTop": "$theme.sizing.scale0",
   "top": "4px",
   "transitionDuration": "$theme.animation.timing100",
@@ -520,6 +520,7 @@ Object {
   "opacity": 0,
   "overflow": "hidden",
   "padding": 0,
+  "position": "absolute",
   "width": 0,
 }
 `;
@@ -532,7 +533,7 @@ Object {
   "fontSize": "$theme.typography.font350.fontSize",
   "fontWeight": "$theme.typography.font350.fontWeight",
   "lineHeight": "24px",
-  "padding": "$theme.sizing.scale200",
+  "paddingLeft": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
@@ -545,7 +546,7 @@ Object {
   "fontSize": "$theme.typography.font350.fontSize",
   "fontWeight": "$theme.typography.font350.fontWeight",
   "lineHeight": "24px",
-  "padding": "$theme.sizing.scale200",
+  "paddingLeft": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
@@ -558,7 +559,7 @@ Object {
   "fontSize": "$theme.typography.font350.fontSize",
   "fontWeight": "$theme.typography.font350.fontWeight",
   "lineHeight": "24px",
-  "padding": "$theme.sizing.scale200",
+  "paddingLeft": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
@@ -571,7 +572,7 @@ Object {
   "fontSize": "$theme.typography.font350.fontSize",
   "fontWeight": "$theme.typography.font350.fontWeight",
   "lineHeight": "24px",
-  "paddingBottom": "$theme.sizing.scale200",
+  "paddingTop": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
@@ -584,7 +585,7 @@ Object {
   "fontSize": "$theme.typography.font350.fontSize",
   "fontWeight": "$theme.typography.font350.fontWeight",
   "lineHeight": "24px",
-  "paddingLeft": "$theme.sizing.scale200",
+  "paddingRight": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
@@ -597,7 +598,7 @@ Object {
   "fontSize": "$theme.typography.font350.fontSize",
   "fontWeight": "$theme.typography.font350.fontWeight",
   "lineHeight": "24px",
-  "paddingRight": "$theme.sizing.scale200",
+  "paddingLeft": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
@@ -610,7 +611,7 @@ Object {
   "fontSize": "$theme.typography.font350.fontSize",
   "fontWeight": "$theme.typography.font350.fontWeight",
   "lineHeight": "24px",
-  "paddingTop": "$theme.sizing.scale200",
+  "paddingBottom": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
diff --git a/src/datepicker/__tests__/datepicker.e2e.js b/src/datepicker/__tests__/datepicker.e2e.js
index 6445386a8..2fb086f1f 100644
--- a/src/datepicker/__tests__/datepicker.e2e.js
+++ b/src/datepicker/__tests__/datepicker.e2e.js
@@ -12,11 +12,13 @@ const {mount, analyzeAccessibility} = require('../../../e2e/helpers');
 
 const selectors = {
   input: 'input',
-  calendar: '[role="grid"]',
-  day: '[aria-label="Sunday, March 10th 2019"]',
-  day2: '[aria-label="Thursday, March 28th 2019"]',
-  day3: '[aria-label="Thursday, February 14th 2019"]',
-  day4: '[aria-label="Monday, April 1st 2019"]',
+  calendar: '[role="application"]',
+  day: '[aria-label="Choose Sunday, March 10th 2019. It\'s available."]',
+  day2: '[aria-label="Choose Thursday, March 28th 2019. It\'s available."]',
+  day3: '[aria-label="Choose Thursday, February 14th 2019. It\'s available."]',
+  day4: '[aria-label="Choose Monday, April 1st 2019. It\'s available."]',
+  day5: '[aria-label="Choose Saturday, March 10th 2018. It\'s available."]',
+  day6: '[aria-label="Choose Monday, July 1st 2019. It\'s available."]',
   leftArrow: '[aria-label="Previous month"]',
   rightArrow: '[aria-label="Next month"]',
   monthSelect: '[data-id="monthSelect"]',
@@ -151,7 +153,7 @@ describe('Datepicker', () => {
     await page.waitFor(selectors.input);
     await page.click(selectors.input);
     await page.waitFor(selectors.calendar);
-    await page.waitFor('[aria-label="Sunday, March 10th 2019"]');
+    await page.waitFor(selectors.day);
     await page.click(selectors.yearSelect);
     const dropdown = `${selectors.yearSelect} ${selectors.selectDropdown}`;
     await page.waitFor(dropdown);
@@ -165,7 +167,7 @@ describe('Datepicker', () => {
     });
     await page.waitFor(dropdown, {hidden: true});
     await page.waitFor(selectors.calendar);
-    await page.waitFor('[aria-label="Saturday, March 10th 2018"]');
+    await page.waitFor(selectors.day5);
   });
 
   it('updates the calendar when a month selected from the dropdown', async () => {
@@ -173,7 +175,7 @@ describe('Datepicker', () => {
     await page.waitFor(selectors.input);
     await page.click(selectors.input);
     await page.waitFor(selectors.calendar);
-    await page.waitFor('[aria-label="Sunday, March 10th 2019"]');
+    await page.waitFor(selectors.day);
     await page.click(selectors.monthSelect);
     const dropdown = `${selectors.monthSelect} ${selectors.selectDropdown}`;
     await page.waitFor(dropdown);
@@ -187,6 +189,6 @@ describe('Datepicker', () => {
     });
     await page.waitFor(dropdown, {hidden: true});
     await page.waitFor(selectors.calendar);
-    await page.waitFor('[aria-label="Monday, July 1st 2019"]');
+    await page.waitFor(selectors.day6);
   });
 });
diff --git a/src/datepicker/__tests__/datepicker.test.js b/src/datepicker/__tests__/datepicker.test.js
index fd0d1a81a..e011d4e7b 100644
--- a/src/datepicker/__tests__/datepicker.test.js
+++ b/src/datepicker/__tests__/datepicker.test.js
@@ -8,7 +8,6 @@ LICENSE file in the root directory of this source tree.
 import React from 'react';
 import {mount} from 'enzyme';
 import {
-  NavigationContainer,
   Unstable_Datepicker as Datepicker,
   Unstable_Calendar as Calendar,
 } from '../index.js';
@@ -59,15 +58,13 @@ describe('Datepicker', () => {
     const PopoverContent = () => renderedPopover.props().content;
 
     const renderedCal = mount(<PopoverContent />);
-    const renderedContainer = renderedCal.find(NavigationContainer).first();
     const renderedCalendar = renderedCal.find(Calendar).first();
 
-    expect(renderedContainer).toExist();
     expect(renderedCalendar).toExist();
 
     expect(renderedCalendar.props().value).toEqual(date);
     expect(renderedCalendar.props().onDayClick).toEqual(onDayClick);
-    expect(renderedContainer.props().onChange).toEqual(
+    expect(renderedCalendar.props().onChange).toEqual(
       component.instance().onChange,
     );
   });
diff --git a/src/datepicker/__tests__/navigation-container.test.js b/src/datepicker/__tests__/navigation-container.test.js
deleted file mode 100644
index 4891db7ba..000000000
--- a/src/datepicker/__tests__/navigation-container.test.js
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
-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 {NavigationContainer, STATE_CHANGE_TYPE} from '../index.js';
-
-describe('NavigationContainer', () => {
-  test('basic render', () => {
-    const props = {
-      highlightedDate: new Date(),
-      onChange: jest.fn(),
-      stateReducer: jest.fn(),
-    };
-    const children = jest.fn();
-
-    shallow(<NavigationContainer {...props}>{children}</NavigationContainer>);
-    expect(children).toHaveBeenCalledTimes(1);
-  });
-
-  describe('Children function receives correct props', () => {
-    const props = {
-      highlightedDate: new Date(),
-      stateReducer: jest.fn(),
-      onChange: jest.fn(),
-      onDayMouseOver: jest.fn(),
-      onDayMouseLeave: jest.fn(),
-    };
-    const children = jest.fn();
-    const eArgs = {date: new Date(), event: {}};
-    const component = shallow(
-      <NavigationContainer {...props}>{children}</NavigationContainer>,
-    );
-    const handlers = [
-      ['onDayMouseOver', true],
-      ['onDayMouseLeave', true],
-      ['onChange', true],
-    ];
-
-    test.each(handlers)('Event handlers', (handler, replaced) => {
-      // event handler is passed to children function
-      const childrenHandler = children.mock.calls[0][0][handler];
-      if (replaced) {
-        expect(childrenHandler).toBe(component.instance()[handler]);
-        expect(childrenHandler).not.toBe(props[handler]);
-      } else {
-        expect(childrenHandler).toBe(props[handler]);
-      }
-      // event handler from props is called
-      // $FlowFixMe
-      childrenHandler(eArgs);
-      expect(props[handler]).toHaveBeenCalledTimes(1);
-      expect(props[handler]).toHaveBeenLastCalledWith(eArgs);
-    });
-  });
-
-  test('stateReducer', () => {
-    const props = {
-      highlightedDate: new Date(2010, 6, 8),
-      stateReducer: jest.fn(),
-    };
-    const children = jest.fn();
-    const state = {highlightedDate: new Date(2010, 6, 8)};
-    const stateUpdated = {highlightedDate: new Date(2010, 6, 1)};
-
-    const component = shallow(
-      <NavigationContainer {...props}>{children}</NavigationContainer>,
-    );
-    props.stateReducer.mockReturnValueOnce(state);
-    component.instance().onKeyDown({
-      key: 'ArrowUp',
-      preventDefault: () => {},
-      stopPropagation: () => {},
-    });
-
-    expect(props.stateReducer).toHaveBeenCalledTimes(1);
-    expect(props.stateReducer.mock.calls[0][0]).toEqual(
-      STATE_CHANGE_TYPE.moveUp,
-    );
-    expect(props.stateReducer.mock.calls[0][1]).toEqual(stateUpdated);
-    expect(props.stateReducer.mock.calls[0][2]).toMatchObject(state);
-    expect(component).toHaveState('highlightedDate', state.highlightedDate);
-  });
-});
diff --git a/src/input/__tests__/__snapshots__/base-input.test.js.snap b/src/input/__tests__/__snapshots__/base-input.test.js.snap
index a6e814b33..fa68cc01e 100644
--- a/src/input/__tests__/__snapshots__/base-input.test.js.snap
+++ b/src/input/__tests__/__snapshots__/base-input.test.js.snap
@@ -24,6 +24,7 @@ Object {
 
 exports[`BaseInput - basic functionality: Base input has correct props 1`] = `
 Object {
+  "aria-describedby": null,
   "aria-invalid": false,
   "aria-label": null,
   "aria-labelledby": null,
diff --git a/src/input/__tests__/__snapshots__/input.test.js.snap b/src/input/__tests__/__snapshots__/input.test.js.snap
index d6341d266..2f0ddba48 100644
--- a/src/input/__tests__/__snapshots__/input.test.js.snap
+++ b/src/input/__tests__/__snapshots__/input.test.js.snap
@@ -2,6 +2,7 @@
 
 exports[`Input - basic functionality: input has correct props 1`] = `
 Object {
+  "aria-describedby": null,
   "aria-invalid": false,
   "aria-label": null,
   "aria-labelledby": null,
@@ -52,6 +53,7 @@ Object {
       }
       $required={false}
       $size="default"
+      aria-describedby={null}
       aria-invalid={false}
       aria-label={null}
       aria-labelledby={null}
@@ -108,6 +110,7 @@ Object {
       }
       $required={false}
       $size="default"
+      aria-describedby={null}
       aria-invalid={false}
       aria-label={null}
       aria-labelledby={null}
@@ -164,6 +167,7 @@ Object {
       }
       $required={false}
       $size="default"
+      aria-describedby={null}
       aria-invalid={false}
       aria-label={null}
       aria-labelledby={null}
diff --git a/src/input/__tests__/__snapshots__/masked-input.test.js.snap b/src/input/__tests__/__snapshots__/masked-input.test.js.snap
index 20cf3a655..4632b8419 100644
--- a/src/input/__tests__/__snapshots__/masked-input.test.js.snap
+++ b/src/input/__tests__/__snapshots__/masked-input.test.js.snap
@@ -46,6 +46,7 @@ Object {
 
 exports[`MaskedInput - basic functionality: Masked input has correct props 1`] = `
 Object {
+  "aria-describedby": null,
   "aria-invalid": false,
   "aria-label": null,
   "aria-labelledby": null,
diff --git a/src/radio/__tests__/__snapshots__/styled-components.test.js.snap b/src/radio/__tests__/__snapshots__/styled-components.test.js.snap
index a1f922729..97d55f3bd 100644
--- a/src/radio/__tests__/__snapshots__/styled-components.test.js.snap
+++ b/src/radio/__tests__/__snapshots__/styled-components.test.js.snap
@@ -6,6 +6,7 @@ Object {
   "opacity": 0,
   "overflow": "hidden",
   "padding": 0,
+  "position": "absolute",
   "width": 0,
 }
 `;
@@ -13,11 +14,11 @@ Object {
 exports[`RadioGroup styled components StyledLabel : has correct styles when  1`] = `
 Object {
   "color": "$theme.colors.foreground",
-  "fontFamily": "$theme.typography.font400.fontFamily",
-  "fontSize": "$theme.typography.font400.fontSize",
-  "fontWeight": "$theme.typography.font400.fontWeight",
-  "lineHeight": "$theme.typography.font400.lineHeight",
-  "padding": "$theme.sizing.scale200",
+  "fontFamily": "$theme.typography.font450.fontFamily",
+  "fontSize": "$theme.typography.font450.fontSize",
+  "fontWeight": "$theme.typography.font450.fontWeight",
+  "lineHeight": "$theme.typography.font450.lineHeight",
+  "paddingLeft": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
@@ -25,11 +26,11 @@ Object {
 exports[`RadioGroup styled components StyledLabel : has correct styles when $isError 1`] = `
 Object {
   "color": "$theme.colors.foreground",
-  "fontFamily": "$theme.typography.font400.fontFamily",
-  "fontSize": "$theme.typography.font400.fontSize",
-  "fontWeight": "$theme.typography.font400.fontWeight",
-  "lineHeight": "$theme.typography.font400.lineHeight",
-  "padding": "$theme.sizing.scale200",
+  "fontFamily": "$theme.typography.font450.fontFamily",
+  "fontSize": "$theme.typography.font450.fontSize",
+  "fontWeight": "$theme.typography.font450.fontWeight",
+  "lineHeight": "$theme.typography.font450.lineHeight",
+  "paddingLeft": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
@@ -37,11 +38,11 @@ Object {
 exports[`RadioGroup styled components StyledLabel : has correct styles when disabled 1`] = `
 Object {
   "color": "$theme.colors.foreground",
-  "fontFamily": "$theme.typography.font400.fontFamily",
-  "fontSize": "$theme.typography.font400.fontSize",
-  "fontWeight": "$theme.typography.font400.fontWeight",
-  "lineHeight": "$theme.typography.font400.lineHeight",
-  "padding": "$theme.sizing.scale200",
+  "fontFamily": "$theme.typography.font450.fontFamily",
+  "fontSize": "$theme.typography.font450.fontSize",
+  "fontWeight": "$theme.typography.font450.fontWeight",
+  "lineHeight": "$theme.typography.font450.lineHeight",
+  "paddingLeft": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
@@ -49,11 +50,11 @@ Object {
 exports[`RadioGroup styled components StyledLabel Label: StyledLabel has correct styles when set to bottom 1`] = `
 Object {
   "color": "$theme.colors.foreground",
-  "fontFamily": "$theme.typography.font400.fontFamily",
-  "fontSize": "$theme.typography.font400.fontSize",
-  "fontWeight": "$theme.typography.font400.fontWeight",
-  "lineHeight": "$theme.typography.font400.lineHeight",
-  "paddingBottom": "$theme.sizing.scale200",
+  "fontFamily": "$theme.typography.font450.fontFamily",
+  "fontSize": "$theme.typography.font450.fontSize",
+  "fontWeight": "$theme.typography.font450.fontWeight",
+  "lineHeight": "$theme.typography.font450.lineHeight",
+  "paddingTop": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
@@ -61,11 +62,11 @@ Object {
 exports[`RadioGroup styled components StyledLabel Label: StyledLabel has correct styles when set to left 1`] = `
 Object {
   "color": "$theme.colors.foreground",
-  "fontFamily": "$theme.typography.font400.fontFamily",
-  "fontSize": "$theme.typography.font400.fontSize",
-  "fontWeight": "$theme.typography.font400.fontWeight",
-  "lineHeight": "$theme.typography.font400.lineHeight",
-  "paddingLeft": "$theme.sizing.scale200",
+  "fontFamily": "$theme.typography.font450.fontFamily",
+  "fontSize": "$theme.typography.font450.fontSize",
+  "fontWeight": "$theme.typography.font450.fontWeight",
+  "lineHeight": "$theme.typography.font450.lineHeight",
+  "paddingRight": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
@@ -73,11 +74,11 @@ Object {
 exports[`RadioGroup styled components StyledLabel Label: StyledLabel has correct styles when set to right 1`] = `
 Object {
   "color": "$theme.colors.foreground",
-  "fontFamily": "$theme.typography.font400.fontFamily",
-  "fontSize": "$theme.typography.font400.fontSize",
-  "fontWeight": "$theme.typography.font400.fontWeight",
-  "lineHeight": "$theme.typography.font400.lineHeight",
-  "paddingRight": "$theme.sizing.scale200",
+  "fontFamily": "$theme.typography.font450.fontFamily",
+  "fontSize": "$theme.typography.font450.fontSize",
+  "fontWeight": "$theme.typography.font450.fontWeight",
+  "lineHeight": "$theme.typography.font450.lineHeight",
+  "paddingLeft": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
@@ -85,11 +86,11 @@ Object {
 exports[`RadioGroup styled components StyledLabel Label: StyledLabel has correct styles when set to top 1`] = `
 Object {
   "color": "$theme.colors.foreground",
-  "fontFamily": "$theme.typography.font400.fontFamily",
-  "fontSize": "$theme.typography.font400.fontSize",
-  "fontWeight": "$theme.typography.font400.fontWeight",
-  "lineHeight": "$theme.typography.font400.lineHeight",
-  "paddingTop": "$theme.sizing.scale200",
+  "fontFamily": "$theme.typography.font450.fontFamily",
+  "fontSize": "$theme.typography.font450.fontSize",
+  "fontWeight": "$theme.typography.font450.fontWeight",
+  "lineHeight": "$theme.typography.font450.lineHeight",
+  "paddingBottom": "$theme.sizing.scale300",
   "verticalAlign": "middle",
 }
 `;
@@ -206,10 +207,10 @@ Object {
   "flexShrink": 0,
   "height": "$theme.sizing.scale700",
   "justifyContent": "center",
-  "marginBottom": "$theme.sizing.scale200",
-  "marginLeft": "$theme.sizing.scale200",
-  "marginRight": "$theme.sizing.scale200",
-  "marginTop": "$theme.sizing.scale200",
+  "marginBottom": "$theme.sizing.scale0",
+  "marginLeft": "$theme.sizing.scale0",
+  "marginRight": "$theme.sizing.scale0",
+  "marginTop": "$theme.sizing.scale0",
   "verticalAlign": "middle",
   "width": "$theme.sizing.scale700",
 }
@@ -221,6 +222,8 @@ Object {
   "cursor": "pointer",
   "display": "flex",
   "flexDirection": "row",
+  "marginBottom": "$theme.sizing.scale200",
+  "marginTop": "$theme.sizing.scale200",
 }
 `;
 
@@ -230,6 +233,8 @@ Object {
   "cursor": "pointer",
   "display": "flex",
   "flexDirection": "column",
+  "marginBottom": "$theme.sizing.scale200",
+  "marginTop": "$theme.sizing.scale200",
 }
 `;
 
@@ -239,6 +244,8 @@ Object {
   "cursor": "pointer",
   "display": "flex",
   "flexDirection": "row",
+  "marginBottom": "$theme.sizing.scale200",
+  "marginTop": "$theme.sizing.scale200",
 }
 `;
 
@@ -248,6 +255,8 @@ Object {
   "cursor": "pointer",
   "display": "flex",
   "flexDirection": "row",
+  "marginBottom": "$theme.sizing.scale200",
+  "marginTop": "$theme.sizing.scale200",
 }
 `;
 
@@ -257,5 +266,7 @@ Object {
   "cursor": "pointer",
   "display": "flex",
   "flexDirection": "column",
+  "marginBottom": "$theme.sizing.scale200",
+  "marginTop": "$theme.sizing.scale200",
 }
 `;
diff --git a/src/table/__tests__/__snapshots__/styled-components.test.js.snap b/src/table/__tests__/__snapshots__/styled-components.test.js.snap
index 9215c46f5..5e66593c7 100644
--- a/src/table/__tests__/__snapshots__/styled-components.test.js.snap
+++ b/src/table/__tests__/__snapshots__/styled-components.test.js.snap
@@ -39,6 +39,54 @@ Object {
 }
 `;
 
+exports[`Table styled components $width property: StyledFilterButton has expected styles when $width property defined 1`] = `
+Object {
+  "border": "none",
+  "padding": "none",
+}
+`;
+
+exports[`Table styled components $width property: StyledFilterContent has expected styles when $width property defined 1`] = `
+Object {
+  "borderColor": "$theme.borders.border300.borderColor",
+  "borderLeft": "none",
+  "borderRight": "none",
+  "borderStyle": "$theme.borders.border300.borderStyle",
+  "borderWidth": "$theme.borders.border300.borderWidth",
+  "maxHeight": "196px",
+  "overflow": "auto",
+  "paddingLeft": "$theme.sizing.scale600",
+  "paddingRight": "$theme.sizing.scale600",
+}
+`;
+
+exports[`Table styled components $width property: StyledFilterFooter has expected styles when $width property defined 1`] = `
+Object {
+  "backgroundColor": "$theme.colors.mono200",
+  "display": "flex",
+  "justifyContent": "space-between",
+  "paddingBottom": "$theme.sizing.scale300",
+  "paddingLeft": "$theme.sizing.scale100",
+  "paddingRight": "$theme.sizing.scale100",
+  "paddingTop": "$theme.sizing.scale300",
+  "width": "216px",
+}
+`;
+
+exports[`Table styled components $width property: StyledFilterHeading has expected styles when $width property defined 1`] = `
+Object {
+  "color": "$theme.colors.mono700",
+  "fontFamily": "$theme.typography.font350.fontFamily",
+  "fontSize": "$theme.typography.font350.fontSize",
+  "fontWeight": "$theme.typography.font350.fontWeight",
+  "lineHeight": "$theme.typography.font350.lineHeight",
+  "paddingBottom": "$theme.sizing.scale500",
+  "paddingLeft": "$theme.sizing.scale600",
+  "paddingRight": "$theme.sizing.scale600",
+  "paddingTop": "$theme.sizing.scale500",
+}
+`;
+
 exports[`Table styled components $width property: StyledHead has expected styles when $width property defined 1`] = `
 Object {
   "backgroundColor": "$theme.colors.tableHeadBackgroundColor",
@@ -61,13 +109,15 @@ Object {
   "borderStyle": "$theme.borders.border300.borderStyle",
   "borderTop": "none",
   "borderWidth": "$theme.borders.border300.borderWidth",
+  "display": "flex",
   "fontFamily": "$theme.typography.font350.fontFamily",
   "fontSize": "$theme.typography.font350.fontSize",
   "fontWeight": "$theme.typography.font350.fontWeight",
+  "justifyContent": "space-between",
   "lineHeight": "$theme.typography.font350.lineHeight",
   "paddingBottom": "$theme.sizing.scale500",
   "paddingLeft": "$theme.sizing.scale600",
-  "paddingRight": 0,
+  "paddingRight": "$theme.sizing.scale600",
   "paddingTop": "$theme.sizing.scale500",
   "width": "100%",
 }
@@ -132,6 +182,54 @@ Object {
 }
 `;
 
+exports[`Table styled components default properties: StyledFilterButton has expected default styles 1`] = `
+Object {
+  "border": "none",
+  "padding": "none",
+}
+`;
+
+exports[`Table styled components default properties: StyledFilterContent has expected default styles 1`] = `
+Object {
+  "borderColor": "$theme.borders.border300.borderColor",
+  "borderLeft": "none",
+  "borderRight": "none",
+  "borderStyle": "$theme.borders.border300.borderStyle",
+  "borderWidth": "$theme.borders.border300.borderWidth",
+  "maxHeight": "196px",
+  "overflow": "auto",
+  "paddingLeft": "$theme.sizing.scale600",
+  "paddingRight": "$theme.sizing.scale600",
+}
+`;
+
+exports[`Table styled components default properties: StyledFilterFooter has expected default styles 1`] = `
+Object {
+  "backgroundColor": "$theme.colors.mono200",
+  "display": "flex",
+  "justifyContent": "space-between",
+  "paddingBottom": "$theme.sizing.scale300",
+  "paddingLeft": "$theme.sizing.scale100",
+  "paddingRight": "$theme.sizing.scale100",
+  "paddingTop": "$theme.sizing.scale300",
+  "width": "216px",
+}
+`;
+
+exports[`Table styled components default properties: StyledFilterHeading has expected default styles 1`] = `
+Object {
+  "color": "$theme.colors.mono700",
+  "fontFamily": "$theme.typography.font350.fontFamily",
+  "fontSize": "$theme.typography.font350.fontSize",
+  "fontWeight": "$theme.typography.font350.fontWeight",
+  "lineHeight": "$theme.typography.font350.lineHeight",
+  "paddingBottom": "$theme.sizing.scale500",
+  "paddingLeft": "$theme.sizing.scale600",
+  "paddingRight": "$theme.sizing.scale600",
+  "paddingTop": "$theme.sizing.scale500",
+}
+`;
+
 exports[`Table styled components default properties: StyledHead has expected default styles 1`] = `
 Object {
   "backgroundColor": "$theme.colors.tableHeadBackgroundColor",
@@ -154,13 +252,15 @@ Object {
   "borderStyle": "$theme.borders.border300.borderStyle",
   "borderTop": "none",
   "borderWidth": "$theme.borders.border300.borderWidth",
+  "display": "flex",
   "fontFamily": "$theme.typography.font350.fontFamily",
   "fontSize": "$theme.typography.font350.fontSize",
   "fontWeight": "$theme.typography.font350.fontWeight",
+  "justifyContent": "space-between",
   "lineHeight": "$theme.typography.font350.lineHeight",
   "paddingBottom": "$theme.sizing.scale500",
   "paddingLeft": "$theme.sizing.scale600",
-  "paddingRight": 0,
+  "paddingRight": "$theme.sizing.scale600",
   "paddingTop": "$theme.sizing.scale500",
   "width": "100%",
 }
diff --git a/src/table/__tests__/filter.test.js b/src/table/__tests__/filter.test.js
new file mode 100644
index 000000000..876f9ef8c
--- /dev/null
+++ b/src/table/__tests__/filter.test.js
@@ -0,0 +1,65 @@
+/*
+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 {Button} from '../../button/index.js';
+import {Filter, StyledFilterButton, StyledFilterContent} from '../index.js';
+
+describe('Table-Filter', () => {
+  it('does not display filter content by default', () => {
+    const wrapper = mount(<Filter>hello</Filter>);
+    const content = wrapper.find(StyledFilterContent);
+    expect(content).not.toExist();
+  });
+
+  it('displays filter content on click', () => {
+    const children = 'hello';
+    const wrapper = mount(<Filter>{children}</Filter>);
+    const icon = wrapper.find(StyledFilterButton);
+    icon.simulate('click');
+
+    const content = wrapper.find(StyledFilterContent);
+    expect(content).toExist();
+    expect(content.text()).toBe(children);
+  });
+
+  it('does not display filter content if disabled', () => {
+    const wrapper = mount(<Filter disabled>hello</Filter>);
+    const icon = wrapper.find(StyledFilterButton);
+    icon.simulate('click');
+
+    const content = wrapper.find(StyledFilterContent);
+    expect(content).not.toExist();
+  });
+
+  it('calls provided onSelectAll handler', () => {
+    const spy = jest.fn();
+    const wrapper = mount(<Filter onSelectAll={spy}>hello</Filter>);
+    const icon = wrapper.find(StyledFilterButton);
+    icon.simulate('click');
+
+    const button = wrapper.find(Button).at(0);
+    button.simulate('click');
+
+    expect(spy).toHaveBeenCalled();
+  });
+
+  it('calls provided onReset handler', () => {
+    const spy = jest.fn();
+    const wrapper = mount(<Filter onReset={spy}>hello</Filter>);
+    const icon = wrapper.find(StyledFilterButton);
+    icon.simulate('click');
+
+    const button = wrapper.find(Button).at(1);
+    button.simulate('click');
+
+    expect(spy).toHaveBeenCalled();
+  });
+});
diff --git a/src/table/__tests__/styled-components.test.js b/src/table/__tests__/styled-components.test.js
index ec3757a7a..68b95928e 100644
--- a/src/table/__tests__/styled-components.test.js
+++ b/src/table/__tests__/styled-components.test.js
@@ -15,6 +15,10 @@ import {
   StyledBody,
   StyledRow,
   StyledCell,
+  StyledFilterButton,
+  StyledFilterContent,
+  StyledFilterHeading,
+  StyledFilterFooter,
   StyledAction,
 } from '../index.js';
 
@@ -26,6 +30,10 @@ describe('Table styled components', () => {
     [StyledBody, 'StyledBody'],
     [StyledRow, 'StyledRow'],
     [StyledCell, 'StyledCell'],
+    [StyledFilterButton, 'StyledFilterButton'],
+    [StyledFilterContent, 'StyledFilterContent'],
+    [StyledFilterHeading, 'StyledFilterHeading'],
+    [StyledFilterFooter, 'StyledFilterFooter'],
     [StyledAction, 'StyledAction'],
   ];
 
diff --git a/src/table/__tests__/table-filter.scenario.js b/src/table/__tests__/table-filter.scenario.js
new file mode 100644
index 000000000..8ec973e19
--- /dev/null
+++ b/src/table/__tests__/table-filter.scenario.js
@@ -0,0 +1,121 @@
+/*
+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 {styled} from '../../styles/index.js';
+import {Checkbox} from '../../checkbox/index.js';
+import {
+  Filter,
+  StyledTable,
+  StyledHead,
+  StyledHeadCell,
+  StyledBody,
+  StyledRow,
+  StyledCell,
+} from '../index.js';
+
+const FilterCheckbox = props => (
+  <Checkbox
+    checked={props.checked}
+    onChange={props.onChange}
+    overrides={{
+      Root: {
+        style: ({$theme}) => ({
+          marginTop: $theme.sizing.scale400,
+          marginBottom: $theme.sizing.scale400,
+        }),
+      },
+      Checkmark: {style: {marginLeft: 0}},
+    }}
+  >
+    {props.children}
+  </Checkbox>
+);
+
+// eslint-disable-next-line flowtype/no-weak-types
+class FilterTable extends React.Component<any, any> {
+  state = {
+    filters: [],
+  };
+
+  FILTER_FUNCTIONS = [...new Array(10)].map((_, i) => row =>
+    row[0] % (i + 1) === 0,
+  );
+
+  onFilter = index => {
+    const isActive = !!this.state.filters[index];
+
+    const filters = [...this.state.filters];
+    if (isActive) {
+      filters[index] = null;
+    } else {
+      filters[index] = this.FILTER_FUNCTIONS[index];
+    }
+    this.setState({filters});
+  };
+
+  handleReset = () => this.setState({filters: []});
+  handleSelectAll = () => this.setState({filters: this.FILTER_FUNCTIONS});
+
+  render() {
+    const filteredData = this.state.filters
+      .filter(Boolean)
+      .reduce((data, filter) => data.filter(filter), this.props.data);
+
+    return (
+      <StyledTable>
+        <StyledHead>
+          <StyledHeadCell>
+            Number
+            <Filter
+              active={!!this.state.filters.filter(Boolean).length}
+              onReset={this.handleReset}
+              onSelectAll={this.handleSelectAll}
+            >
+              {this.FILTER_FUNCTIONS.map((_, index) => (
+                <FilterCheckbox
+                  key={index}
+                  checked={!!this.state.filters[index]}
+                  onChange={() => this.onFilter(index)}
+                >
+                  {`Divisible by ${index + 1}`}
+                </FilterCheckbox>
+              ))}
+            </Filter>
+          </StyledHeadCell>
+          <StyledHeadCell>Title</StyledHeadCell>
+        </StyledHead>
+
+        <StyledBody>
+          {filteredData.map((row, index) => (
+            <StyledRow key={index}>
+              {row.map((cell, cellIndex) => (
+                <StyledCell key={cellIndex}>{cell}</StyledCell>
+              ))}
+            </StyledRow>
+          ))}
+        </StyledBody>
+      </StyledTable>
+    );
+  }
+}
+
+const Container = styled('div', {
+  height: '500px',
+});
+
+export const name = 'table-filter';
+
+export const component = () => {
+  const FILTER_DATA = [...new Array(100)].map((_, i) => [i, 'row title']);
+  return (
+    <Container>
+      <FilterTable data={FILTER_DATA} />
+    </Container>
+  );
+};
diff --git a/src/tag/__tests__/__snapshots__/styled-components.test.js.snap b/src/tag/__tests__/__snapshots__/styled-components.test.js.snap
index eec28a817..c54e40283 100644
--- a/src/tag/__tests__/__snapshots__/styled-components.test.js.snap
+++ b/src/tag/__tests__/__snapshots__/styled-components.test.js.snap
@@ -68,6 +68,38 @@ Object {
 }
 `;
 
+exports[`Tag styled components StyledRoot : StyledRoot has correct styles when $closable 1`] = `
+Object {
+  ":hover": Object {
+    "backgroundColor": null,
+  },
+  "alignItems": "center",
+  "backgroundColor": null,
+  "borderColor": undefined,
+  "borderRadius": "$theme.borders.radius400",
+  "borderWidth": "1px",
+  "boxSizing": "border-box",
+  "color": undefined,
+  "cursor": "auto",
+  "display": "inline-flex",
+  "fontFamily": "$theme.typography.font250.fontFamily",
+  "fontSize": "$theme.typography.font250.fontSize",
+  "fontWeight": "$theme.typography.font250.fontWeight",
+  "height": "$theme.sizing.scale800",
+  "justifyContent": "space-between",
+  "lineHeight": "$theme.typography.font250.lineHeight",
+  "marginBottom": "5px",
+  "marginLeft": "5px",
+  "marginRight": "5px",
+  "marginTop": "5px",
+  "opacity": null,
+  "paddingBottom": "$theme.sizing.scale100",
+  "paddingLeft": "$theme.sizing.scale500",
+  "paddingRight": "$theme.sizing.scale500",
+  "paddingTop": "$theme.sizing.scale100",
+}
+`;
+
 exports[`Tag styled components StyledRoot : StyledRoot has correct styles when $disabled 1`] = `
 Object {
   ":hover": Object {
diff --git a/src/tag/__tests__/styled-components.test.js b/src/tag/__tests__/styled-components.test.js
index 6b3f77e6f..0aaddf94d 100644
--- a/src/tag/__tests__/styled-components.test.js
+++ b/src/tag/__tests__/styled-components.test.js
@@ -11,21 +11,24 @@ import {StyledRoot, StyledAction} from '../index.js';
 
 describe('Tag styled components', () => {
   describe('StyledRoot', () => {
-    test.each([[''], ['$disabled'], ['$isFocused'], ['$isActive']])(
-      '',
-      prop => {
-        const props = {};
-        props[prop] = true;
-        const component = shallow(
-          <StyledRoot {...props}>
-            <div />
-          </StyledRoot>,
-        );
-        expect(component.instance().getStyles()).toMatchSnapshot(
-          'StyledRoot has correct styles when ' + prop,
-        );
-      },
-    );
+    test.each([
+      [''],
+      ['$disabled'],
+      ['$isFocused'],
+      ['$isActive'],
+      ['$closable'],
+    ])('', prop => {
+      const props = {};
+      props[prop] = true;
+      const component = shallow(
+        <StyledRoot {...props}>
+          <div />
+        </StyledRoot>,
+      );
+      expect(component.instance().getStyles()).toMatchSnapshot(
+        'StyledRoot has correct styles when ' + prop,
+      );
+    });
   });
 
   describe('StyledAction', () => {
diff --git a/src/textarea/__tests__/__snapshots__/textarea.test.js.snap b/src/textarea/__tests__/__snapshots__/textarea.test.js.snap
index 8c7833e0b..54856d196 100644
--- a/src/textarea/__tests__/__snapshots__/textarea.test.js.snap
+++ b/src/textarea/__tests__/__snapshots__/textarea.test.js.snap
@@ -2,6 +2,7 @@
 
 exports[`Textarea Basic functionality: Textarea has correct props 1`] = `
 Object {
+  "aria-describedby": null,
   "aria-invalid": false,
   "aria-label": null,
   "aria-labelledby": null,
@@ -27,6 +28,7 @@ Object {
 exports[`Textarea Basic functionality: textarea renders BaseInput with correct props passed 1`] = `
 Object {
   "adjoined": "none",
+  "aria-describedby": null,
   "aria-label": null,
   "aria-labelledby": null,
   "autoFocus": false,
@@ -62,6 +64,7 @@ Object {
 exports[`Textarea With component overrides: components overrides get passed to the BaseInput 1`] = `
 Object {
   "adjoined": "none",
+  "aria-describedby": null,
   "aria-label": null,
   "aria-labelledby": null,
   "autoFocus": false,