govuk-frontend as React components.
This is proof of concept, showing how govuk-frontend can be used as CSS modules via a set of React components that is published to npm, in a way that is compatible with create-react-app, Next.js, Final Form, Formik, React Router and Reach Router, with support for tree shaking and code splitting/lazy loading.
See:
govuk-frontend does not 100% support being used as CSS Modules, though there the potential for this to be added in future:
alphagov/govuk-design-system-architecture#12
One aim of this project is to create a library that has all the features of govuk-react, but is powered by govuk-frontend. One of these features is path splitting or critical extraction of styles when used in a 'standard' React build system such as those used by create-react-app or next.js.
Path splitting for CSS is coming to next.js soon:
- RFC CSS Support vercel/next.js#8626
As raised in the RFC for this feature, critically extracting CSS is potentially error prone due to the potential to extract global styles. However, when using CSS Modules, next.js is able to analyse what styles are not global and extract these. As such, next.js will only provide path splitting and critical extraction for CSS Modules. This is a reasonable approach and means in order to achieve this we need to use govuk-frontend as a set of CSS Modules and then identify what is broken and perhaps manually fix it, or raise as an issue on govuk-frontend (see list of issues below).
Common conventions used when deciding how to rewrite the nunjucks templates:
When looking at govuk-frontend BEM class names:
- Blocks become exported components
- Elements become subcomponents either inside the same file as the main component or in an elements subfolder, and exported as a property of the main component.
- Modifiers become prop names of the component or subcomponent.
e.g. a class name of govuk-my-block__my-element--my-modifier
implies:
- There is a component
<MyBlock />
available by callingimport { MyBlock } from 'govuk-frontend-react'
that sits at/src/{type}/my-block/index.js
- There is a subcomponent
<MyBlock.MyElement />
that is defined either in/src/{type}/my-block/index.js
or in/src/{type}/my-block/elements/my-element.js
<MyBlock.MyElement />
accepts a propmyModifier
{type}
can beobjects
orcomponents
following govuk-frontend/ITCSS classification.
e.g. a class name of govuk-radios__conditional--hidden
implies:
- There is a component
<Radios />
available by callingimport { Radios } from 'govuk-frontend-react'
that sits at/src/components/radios/index.js
- There is a subcomponent
<Radios.Conditional />
that is defined either in/src/components/radios/index.js
or in/src/components/radios/elements/conditional.js
<Radios.Conditional />
accepts a prophidden
TODO: props (modifiers) sent to component should be made available to overrding sub components (elements) - how to do this?
=> if you want to intercept the modifiers as props then provide custom elements/components (CSSinJS with styled function) => if you just want to provide className lookup then use classNames (CSS Modules)/CSSinJS with css function
BEM convention classname exceptions:
- govuk-label-wrapper
Where our React prop names differ from the nunjucks macro option names.
nunjucks | react |
---|---|
element | as |
attributes | rest props (where appropriate) |
text | children (where appropriate) |
html | children (where appropriate) |
describedBy | aria-describedby |
This conversion can be seen in more detail in tests/render.js
(though this file needs cleaning up at time of writing).
Works! Including:
- Lazy loading/path splitting
- Tree shaking of CSS
See https://github.com/penx/govuk-frontend-react-example
I aim to complete the following as part of the PoC:
- Button
- Header compatible with React Router
- Support for js-enabled class, used by Header, plus any associated JS required by Header
- Input with Final Form and Formik examples.
- Date Input with Final Form and Formik examples.
- Radios
- Tables
- Error summary with Formik and Final Form examples, following govuk design system guidelines.
- Reach router fixtures
- Code coverage checks in CI
- 100% code coverage
- Meaningful unit tests
- Use/match govuk-frontend template tests
- Prettier
- Flow
- Create React App example with lazy loading/path splitting and tree shaking
- Next.js example
- Support for path splitting blocked by zeit/next-plugins#190 (related)
- Server side only example with form submit and display of form errors
- Separate styling and templates, e.g. so the same templates could be used by plain CSS classes, CSS modules or CSSinJS classes passed in as props
- Non CSS modules version
- CSSinJS PoC
- Compile all .module.scss to .module.css and include in npm package to prevent conflicts if using multiple versions
- Review remaining TODO: and put in to GitHub issues
- More stories/use cases for radio buttons
Other TODO:
- Script to convert govuk-frontend attributes to prop types (or Flow types)
- Export standalone templates, CSS includes and CSS modules separately, e.g.
import { Button } from 'govuk-frontend-react'
import { Button } from 'govuk-frontend-react-templates'
,import { Button } from 'govuk-frontend-react-modules'
- Use Jest snapshots from govuk-frontend rather than manually copying
- refactor render.js so that it is easier to scale
- Greenkeeper and CI releases on template spec, feeding in to Greenkeeper on templates => automated flagging of new releases of govuk-frontend
Things that I'm not 100% on how to deal with:
- custom CSS classes such as "width-2" class on Input being passed in as props but are actually CSS modules - could look up via a classNames object first?
- should we allow shortcuts so that
label={{children: 'Label'}}
can just be specified aslabel="Label"
? Or should we separate in to two props,label
andlabelProps
? - should
elements
andclassNames
props use the context API to ensure ancestor elements can always access without prop drilldown?