amplify-ui icon indicating copy to clipboard operation
amplify-ui copied to clipboard

feat(ui): component theming

Open dbanksdesign opened this issue 10 months ago • 1 comments

Customer problems

1. Cannot theme a component when there is no design token available

Developers can theme any part of our components in any way, but only in CSS. If they use a theme object they are constrained by the design tokens we expose. For example, a developer cannot add a box shadow on our buttons at the theme level without using plain CSS. It is cumbersome to do some stuff with the theme object and other stuff with plain CSS.

2. No intellisense/type-safety for referencing tokens

Token references in a theme are just strings and do not have intellisense or autocomplete. The only way to know if you referenced a token correctly is to run the app locally and see if it is being styled correctly.

const theme = createTheme({
  tokens: {
    components: {
      badge: {
        // whoops, misspelled! but still valid
        paddingHorizontal: '{space.lorge}'
      }
    }
  }
});

3. No easy way to create custom primitives

https://github.com/aws-amplify/amplify-ui/issues/2376

Customers basically have to write their own CSS and use our CSS variables if they want to integrate with our theming system.

4. No RSC support

Not a huge deal, but really since the theme is just CSS there is no reason that it shouldn’t be able to be a server component.

5. Splitting up a theme is not possible

For larger themes with a lot of component customization, a theme object can get very long. Developers might want to split up the theme into component themes like a button theme. Today that is not possible in our types.

CleanShot 2023-10-02 at 20 45 53@2x

DX

Type-safety for theming components in JS

CleanShot 2024-06-25 at 14 12 03

Type-safety for custom component themes/classnames

CleanShot 2024-06-25 at 13 14 30

Description of changes

These are all additive changes so this PR has no breaking changes.

Adding next-app-router example app

This is used to verify RSC-compliant code and is a testing ground for the new theming capabilities and any future RSCs we build.

Adding a /server export path in the ui-react package for RSC

Importing anything as an RSC will look at the entire module whether or not code is used, so we have to have a separate entry point for any RSC-compliant code. Right now that is only this new theming stuff, but we could expand it in the future. It is currently re-exporting the theming functions from the ui package and a new <Theme /> RSC.

<Theme /> RSC

import { Theme } from '@aws-amplify/ui-react/server';
import { theme } from '@/theme';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <Theme theme={theme} colorMode="system">
        {children}
    </Theme>
  )
}

The Theme RSC acts like the ThemeProvider without the React context. It takes the same props as ThemeProvider. Because this is an RSC the CSS is generated on the server (or at build time) and shipped to the client.

defineComponentTheme

This function is used to: fully theme built-in components, as well as define the theme/styles for custom components.

import { defineComponentTheme } from '@aws-amplify/ui-react/server';

export const buttonTheme = defineeComponentTheme({
  // because 'button' is a built-in component, we get type-safety and hints
  // based on the theme shape of our button
  name: 'button',
  theme: (tokens) => {
    return {
      textAlign: 'center',
      padding: tokens.space.xl,
      _modifiers: {
        primary: {
          backgroundColor: tokens.colors.primary[20],
        },
      },
    };
  },
});

The defineComponentTheme function doesn't really do anything itself. You will need to pass it to the createTheme function. (keep reading)

Add components attribute in createTheme

export const theme = createTheme({
  name: 'my-theme',
  components: [
    buttonTheme,
    customComponentTheme,
  ]
})

Removing direct dependency on style-dictionary

This was a tough one for me, but I wanted to keep this PR within our existing bundle size limits. We were only importing specific functions from style-dictionary which also included an ES6 polyfill, which we don't need. So I copied the code over and removed the ES6 polyfill stuff. This change actually decreased the overall bundle size, so thats nice.

Issue #, if available

Description of how you validated changes

Added a Next app router example to test the changes on. Additionally, tested the docs changes.

Checklist

  • [ ] Have read the Pull Request Guidelines
  • [ ] PR description included
  • [ ] Relevant documentation is changed or added (and PR referenced)
  • [ ] yarn test passes and tests are updated/added
  • [ ] No side effects or sideEffects field updated

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

dbanksdesign avatar Apr 18 '24 22:04 dbanksdesign

🦋 Changeset detected

Latest commit: 217d7b61ec672b3c4e5a07fd59da70405e8e5c9a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
@aws-amplify/ui Minor
@aws-amplify/ui-react Minor
@aws-amplify/ui-react-auth Patch
@aws-amplify/ui-react-core-auth Patch
@aws-amplify/ui-react-core-notifications Patch
@aws-amplify/ui-react-core Patch
@aws-amplify/ui-react-liveness Patch
@aws-amplify/ui-react-native-auth Patch
@aws-amplify/ui-react-native Patch
@aws-amplify/ui-react-notifications Patch
@aws-amplify/ui-react-storage Patch
@aws-amplify/ui-vue Patch
@aws-amplify/ui-angular Patch
@aws-amplify/ui-react-geo Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

changeset-bot[bot] avatar Apr 18 '24 22:04 changeset-bot[bot]

Great features being added to the theming capabilities, I didn't see any updates to the docs in this PR should they be included here or is there a separate PR with docs updates to document this new functionality?

jacoblogan avatar Aug 08 '24 17:08 jacoblogan

Should the new example be added to the -example commands in the root package.json?

jordanvn avatar Aug 08 '24 17:08 jordanvn

@jacoblogan I intentionally left out docs from this PR as it is an experimental API and want to get this in so the Amplify console can start using it. Docs are coming a little later after we iron out the kinks.

dbanksdesign avatar Aug 08 '24 19:08 dbanksdesign