{
    "componentChunkName": "component---src-templates-post-js",
    "path": "/sticky-table-headers/",
    "result": {"data":{"mdx":{"body":"var _excluded = [\"components\"];\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"title\": \"The Complexities of Fixed Table Headers\",\n  \"date\": \"2021-03-19T00:00:00.000Z\",\n  \"summary\": \"Fixed navigations in tables have notoriously been tricky. Even with new features like position sticky, there are still drawbacks. In this post, we'll look at handling fixed headers in a scrollable table component.\",\n  \"image\": \"../images/bringing-swiftui-stacks-to-the-web.png\",\n  \"tags\": [\"table\", \"position\", \"sticky\", \"react\"]\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, _excluded);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"First, we\\u2019re going to forgo the standard table markup and use divs with respective aria roles. Reasons for this are:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"The default Table Layout is limiting so we want to use Flexbox.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Changing the display properties will break accessibility.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Since we have to add aria roles, we can use divs for a clean slate to build our styles upon, making our component easier to drop-in.\")), mdx(Disclaimer, {\n    mdxType: \"Disclaimer\"\n  }, \"Note, if you are only dealing with simple styles, you most likely don't need to do this. Always opt for native elements when you can!\"), mdx(\"h2\", {\n    \"id\": \"getting-started\"\n  }, \"Getting Started\"), mdx(\"p\", null, \"To start, this will be our basic markup:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-jsx\"\n  }, \"<div role=\\\"table\\\">\\n  <div role=\\\"rowgroup\\\">\\n    <div role=\\\"row\\\">\\n      <div role=\\\"columnheader\\\">Name</div>\\n      <div role=\\\"columnheader\\\">Twitter</div>\\n    </div>\\n  </div>\\n  <div role=\\\"rowgroup\\\">\\n    <div role=\\\"row\\\">\\n      <div role=\\\"cell\\\">Travis</div>\\n      <div role=\\\"cell\\\">@souporserious</div>\\n    </div>\\n  </div>\\n<div>\\n\")), mdx(\"p\", null, \"See the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://www.w3.org/TR/wai-aria-1.2/#table\"\n  }, \"W3 ARIA Guidelines\"), \" to learn more about how these values work.\"), mdx(\"p\", null, \"If you\\u2019ve ever tried to add anything beyond simple styles to a table element, like overflow, you\\u2019ll notice things don\\u2019t seem to work quite as you\\u2019d expect.\"), mdx(\"p\", null, \"In their prime, Table Layouts were the only method for layout on the web. Fast forward to today, we have a clunky old API that doesn\\u2019t hold up as well as newer methods like Flexbox and Grid. This is where ARIA roles shine and allow us to work around these quirks. However, this does come with drawbacks, we can no longer opt into things like auto width layouts and instead need to define explicit sizes for each column.\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-jsx\"\n  }, \"function Table() {\\n  return (\\n    <div role=\\\"table\\\">\\n      <div role=\\\"rowgroup\\\">\\n        <div role=\\\"row\\\">\\n          <div role=\\\"columnheader\\\">Name</div>\\n          <div role=\\\"columnheader\\\">Twitter</div>\\n        </div>\\n      </div>\\n      <div role=\\\"rowgroup\\\">\\n        <div role=\\\"row\\\">\\n          <div role=\\\"cell\\\">Travis</div>\\n          <div role=\\\"cell\\\">@souporserious</div>\\n        </div>\\n      </div>\\n    </div>\\n  )\\n}\\n\")), mdx(\"h2\", {\n    \"id\": \"fixed-header\"\n  }, \"Fixed Header\"), mdx(\"p\", null, \"We want a sticky table header relative to the viewport and the ability to scroll horizontally if there isn\\u2019t enough room. Unfortunately, browsers don\\u2019t give us a ton of control. All is well until we introduce overflow scroll, which might be likely if you\\u2019re not relying on the full browser width.\"), mdx(\"p\", null, \"Since we use overflow scroll, this messes with our position sticky attribute. Until browsers give us better control of where we want to apply sticky positioning, we need to add event listeners for scroll and sync between the header and body.\"), mdx(\"p\", null, \"However, this does come with drawbacks, we can no longer opt into things like auto width layouts. This can be achieved by measuring each cell to determine the proper column width or in the future once there is better subgrid support Grid will be able to be used here. Since Grid uses a flat structure and rows/columns are implicit we cannot apply the proper aria roles.\"), mdx(\"h2\", {\n    \"id\": \"syncing-scrollbars\"\n  }, \"Syncing scrollbars\"), mdx(\"p\", null, \"Enter the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/souporserious/use-scroll-sync\"\n  }, \"useScrollSync\"), \" hook. This hook will sync the scroll positions of multiple refs:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-jsx\"\n  }, \"function Table({ columns, data }) {\\n  const headerRef = React.useRef(null)\\n  const bodyRef = React.useRef(null)\\n\\n  useScrollSync(headerRef, bodyRef)\\n\\n  return (\\n    <div role=\\\"table\\\">\\n      <div ref={headerRef} role=\\\"rowgroup\\\">\\n        ...\\n      </div>\\n      <div ref={bodyRef} role=\\\"rowgroup\\\">\\n        ...\\n      </div>\\n    </div>\\n  )\\n}\\n\")), mdx(\"p\", null, \"This will listen for scroll events among both the header and body and synchronize them as best possible to make it feel like one scrolling experience.\"), mdx(\"p\", null, \"As an extra we\\u2019ll add a little bit of CSS to hide the scrollbars:\"), mdx(\"p\", null, \"The way to fix this is usin\"), mdx(\"p\", null, \"And that\\u2019s it, now our table component can utilize Flex layouts as well as fixing the header for larger tables that scroll.\"), mdx(\"h2\", {\n    \"id\": \"conclusion\"\n  }, \"Conclusion\"), mdx(\"p\", null, \"While default Tables will most certainly be difficult to style beyond anything basic. With newer layout methods like Flexbox and Grid we can save ourselves the hard tasks. In future posts we\\u2019ll explore how to enhance accessibility for more complex use-cases like nested rows or using Grid.\"));\n}\n;\nMDXContent.isMDXComponent = true;","excerpt":"First, we're going to forgo the standard table markup and use divs with respective aria roles. Reasons for this are: The default Table…","fields":{"readingTime":{"text":"4 min read"},"slug":"/sticky-table-headers/"},"frontmatter":{"date":"03/19/2021","summary":"Fixed navigations in tables have notoriously been tricky. Even with new features like position sticky, there are still drawbacks. In this post, we'll look at handling fixed headers in a scrollable table component.","tags":["table","position","sticky","react"],"title":"The Complexities of Fixed Table Headers","image":{"childImageSharp":{"fluid":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACdklEQVQoz4WM20uTARjGvz8iiITKgsIKwptuuiijLAjTziZhmkhihWWUiYecpqISBIqhzXnMtIOuQic6y9Q8XajzUCS6A9u+bW7zFORM3W/xzWUFRhc/nvd9n+d9BP80Dfuzx9iTOcq2VA1bUzRsThpiu2/ekjyMX/LwembTvSF2ZYwQmDPO7oxR9mWNESAbZUfaCDvTRxD8UjRkqURKu+2kvDWRqRKRNYkkKU0Udti4rzQRW6vnRr2BArWVvDYLsmYRRa+Dhy0ij9qtRFXr8E8fISBrDCEgZ5ww+RSRNXrOKrSEV+qQtVhIfmcmutZAQoOR1CaRoMIJ7x5bZ+BihY5Q+RQRVTovQUUT7M39TGD+F4Twaj25aivXXhqJqNF79ZbSxOVnekLKtNxsMHGhUkdkrYHsNqv3HqrQktwk8qDFQuIbE2kqC8Elkxx4/BUh6oWRJ31OCjrtyAdm1ue8jmniGk3kf5xGprYR88pI6YCTwh4H8Uoz6a1WinudlPQ7Ke13IvUcLJ5EOF6u50iZjqMKHYflWoLLdYRUG7yE1qzpKd98skrvRdqPla/lD8m13n/p70SFHiGszsjpP8jrdlClmaNscA7F0N+U+1TyszsdhD43cqb+96/UJYQ3ikQoRc69NnO3fZo+8yIam4tBq4uhDZDukv/J+J2EVhvnG8xcUopIPRJCtMrGlWYbMS02Biwu5pfcnvkl9y/dkDmf32VcJEpl8xLtUyH+g4MYtZ2nYwssrrg9y6uw4v4/y2749sPtKdIscFVtR+qJe+9ASOyZ5XrXLJ0WF4BHCq+68bjx/BPJl3J48LSaXMR1znCnd5bbPbP8BCBusC8cmWD+AAAAAElFTkSuQmCC","aspectRatio":1.9047619047619047,"src":"/static/feded2aa7e39bddfa7a008b9f7347b6f/ee604/bringing-swiftui-stacks-to-the-web.png","srcSet":"/static/feded2aa7e39bddfa7a008b9f7347b6f/69585/bringing-swiftui-stacks-to-the-web.png 200w,\n/static/feded2aa7e39bddfa7a008b9f7347b6f/497c6/bringing-swiftui-stacks-to-the-web.png 400w,\n/static/feded2aa7e39bddfa7a008b9f7347b6f/ee604/bringing-swiftui-stacks-to-the-web.png 800w,\n/static/feded2aa7e39bddfa7a008b9f7347b6f/f3583/bringing-swiftui-stacks-to-the-web.png 1200w","sizes":"(max-width: 800px) 100vw, 800px"}}}}},"previous":null,"next":null},"pageContext":{"id":"14080a1e-2495-5460-8034-db7f15c36746"}},
    "staticQueryHashes": ["3764592887"]}