Ory Elements is a component library that makes building login, registration and account pages for Ory a breeze.
- Reduces time to add complex auth flows to your customer experience, including multi-factor authentication and account recovery
- Themeable and modular - use only what you need from it
- Works with the React ecosystem (NextJS, React SPA, Preact SPA)
- Works with the Express based ecosystem
- Dynamically adapts the user interface to your Ory identity schema, sign-in and flow configuration
Ory Elements supports integrating with:
- React
- Preact
- Express.js (experimental)
Install Ory Elements into your existing React / Preact or Express.js application. You can find example apps here
React
npm install @ory/elements --save
Preact
npm install @ory/elements-preact --save
Express.js
npm install @ory/elements-markup --save
Dive into Ory Elements with our react
and preact
SPA example applications,
located in the examples/
directory.
To run the example application you will need a couple of things:
Clone this repository and set up the React example.
git clone [email protected]:ory/elements
npm run initialize
npm run build:clean
cd examples/react-spa
export VITE_ORY_SDK_URL=http://localhost:4000
npm run dev -- --port 3000
Now run the Ory CLI tunnel.
ory tunnel http://localhost:3000 --project <project-slug> --dev
The tunnel will now mirror the Ory APIs under http://localhost:4000
which we
have explicitly told our React app to use through the VITE_ORY_SDK_URL
export.
Now you can see Ory Elements in action by opening http://localhost:3000 in your browser!
Before v0.0.1-beta.1, Ory Elements exposed a singular style.css
file which
contained all the required fonts and icons necessary to work out of the box.
This was convenient for elements to work out of the box, but caused the bundle
size to be larger than necessary, especially for applications that only use a
few components or their own icons and fonts.
The new version of Ory Elements now only exposes the CSS for the components in
the style.css
file, and the rest of the CSS are optional and can be imported
individually.
// Ory Elements
// optional global css reset
import "@ory/elements/assets/normalize.css"
// optional fontawesome icons
import "@ory/elements/assets/fa-brands.min.css"
import "@ory/elements/assets/fa-solid.min.css"
import "@ory/elements/assets/fontawesome.min.css"
// optional fonts
import "@ory/elements/assets/inter-font.css"
import "@ory/elements/assets/jetbrains-mono-font.css"
// required styles for Ory Elements
import "@ory/elements/style.css"
Explore the Ory Elements via Storybook!
Clone this repository and run:
npm run initialize
npm run build
# or `npm run build:clean` to ensure no packages have cached versions
npm run storybook
Write a new component inside the src/react-components
directory with its
corresponding CSS in src/theme
. Check it out by writing a new story for the
component in the src/stories
folder.
Add a test to verify the component works correctly by creating a new file next
to the component file with the same name and an added *.spec.ts
extension. All
E2E and component tests are written in Playwright.
Example Apps
To contribute an example application, please add it to the examples/
folder.
To ensure the example works correctly within the Lerna build system, add the
elements
package to the example package.json
with an asterisk *
as the
version.
Below is an example of how you should add the package.
...
"devDependencies": {
"@ory/elements": "*"
}
...
Ory Elements uses Lerna to bundle each package in the Ory Elements mono-repository. This also helps with package management and build caching. Lerna also publishes the code to the public npm registry for us.
Lerna also use Nx to build the packages in parallel.
Vanilla-Extract is used to strongly type the
CSS, a type of CSS-in-JS
library which generates a static CSS file for us when
the library is built. This means we can manage our CSS and reduce a lot of
typing, since it can generate the CSS classes for us.
Here is an example of vanilla-extract in action!
export const dividerStyle = recipe({
base: {
display: "block",
textAlign: "center",
overflow: "hidden",
boxSizing: "border-box",
border: 0,
borderTop: `${pxToRem(4)} solid`,
borderColor: oryTheme.border.def,
width: pxToRem(64),
},
variants: {
sizes: {
fullWidth: {
width: "100%",
},
},
},
})
Generated JS function.
var dividerStyle = createRuntimeFn({
defaultClassName: "_3ldkmt0",
variantClassNames: { sizes: { fullWidth: "_3ldkmt1" } },
defaultVariants: {},
compoundVariants: [],
})
And the CSS.
gO .\_3ldkmt0 {
display: block;
text-align: center;
overflow: hidden;
box-sizing: border-box;
border: 0;
border-top: 0.25rem solid;
border-color: var(--ory-theme-border-def);
width: 4rem;
}
.\_3ldkmt1 {
width: 100%;
}
Vanilla-Extract also provides us theme variables which we can give static names. This means we can overwrite them inside the project consuming the library!
:root {
--ory-theme-font-family: Inter;
--ory-theme-font-style: normal;
--ory-theme-accent-def: #3d53f5;
--ory-theme-accent-muted: #6475f7;
--ory-theme-accent-emphasis: #3142c4;
--ory-theme-accent-disabled: #e0e0e0;
--ory-theme-accent-subtle: #eceefe;
--ory-theme-foreground-def: #171717;
--ory-theme-foreground-muted: #616161;
--ory-theme-foreground-subtle: #9e9e9e;
--ory-theme-foreground-disabled: #bdbdbd;
--ory-theme-foreground-on-dark: #ffffff;
--ory-theme-foreground-on-accent: #ffffff;
--ory-theme-foreground-on-disabled: #e0e0e0;
--ory-theme-background-surface: #ffffff;
--ory-theme-background-canvas: #fcfcfc;
--ory-theme-error-def: #9c0f2e;
--ory-theme-error-subtle: #fce8ec;
--ory-theme-error-muted: #e95c7b;
--ory-theme-error-emphasis: #df1642;
--ory-theme-success-emphasis: #18a957;
--ory-theme-border-def: #e0e0e0;
--ory-theme-text-def: #ffffff;
--ory-theme-text-disabled: #757575;
--ory-theme-input-background: #ffffff;
--ory-theme-input-disabled: #e0e0e0;
--ory-theme-input-placeholder: #9e9e9e;
--ory-theme-input-text: #424242;
}
Inside our components we provide the <ThemeProvider />
which exposes the
themeOverrides
property so that you can implement your own theme.
// Ory Elements
// optional global css reset
import "@ory/elements-preact/assets/normalize.css"
// optional fontawesome icons
import "@ory/elements-preact/assets/fa-brands.min.css"
import "@ory/elements-preact/assets/fa-solid.min.css"
import "@ory/elements-preact/assets/fontawesome.min.css"
// optional fonts
import "@ory/elements-preact/assets/inter-font.css"
import "@ory/elements-preact/assets/jetbrains-mono-font.css"
// required styles for Ory Elements
import "@ory/elements-preact/style.css"
const Main = () => {
return (
<ThemeProvider themeOverrides={customTheme}>
<Router>
<Route path="/" component={Dashboard} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Register} />
<Route path="/verification" component={Verification} />
<Route path="/recovery" component={Recovery} />
<Route path="/settings" component={Settings} />
</Router>
</ThemeProvider>
)
}
For Express.js the library also exports a helper function which registers all the CSS the library produces.
import express, { Application } from "express"
import { assignInlineVars } from "@vanilla-extract/dynamic"
import { oryTheme, Theme } from "../theme"
export const RegisterOryElementsExpress = (app: Application, theme: Theme) => {
app.use("/theme.css", (req, res) => {
res.header("Content-Type", "text/css")
res.send(
`body {${assignInlineVars(oryTheme, {
...oryTheme,
...theme,
}).toString()}}`,
)
})
app.use("/", express.static("node_modules/@ory/elements/dist"))
}
Which exposes all the relevant CSS files for us which we just import in our HTML page:
<link rel="stylesheet" href="style.css" /> // the default theme variables
<link rel="stylesheet" href="theme.css" /> // the overidden theme variables
We can then reference a component through handlebars' helper functions that return pure HTML.
// Render the data using a view (e.g. Jade Template):
res.render("login", {
...flow,
typography: (text: string, size: any, color: any) =>
Typography({
children: text,
type: "regular",
size,
color,
}),
card: UserAuthCard({
title: !(flow.refresh || flow.requested_aal === "aal2")
? "Sign In"
: "Two-Factor Authentication",
flow: flow as SelfServiceFlow,
flowType: "login",
cardImage: "ory-logo.svg",
additionalProps: {
forgotPasswordURL: "recovery",
signupURL: initRegistrationUrl,
logoutURL: logoutUrl,
},
}),
})
Ory Elements solely relies on React components since they are easy to write and provides support to a large React based ecosystem. The project then bundles these components to their respective needs. An example is bundling for Preact which you can find in the packages' folder. It uses the React components directly in this case, but bundles it specifically for Preact support.
Each component relies on some CSS styles, which are located in the theme directory. To understand how this works, please refer to the CSS System
Express.js is an edge-case which requires the React components to be wrapped by
the ReactDOMServer
to produce static HTML. This essentially does server-side
rendering of the components and removes any client-side JavaScript. Each
component needs to be wrapped by ComponentWrapper
which essentially uses
ReactDOMServer
. The elements-markup
package then bundles the React library
with it so that the React code lives with the component library.
Here is an example of exporting the UserAuthCard
.
import {
UserAuthCard as userAuthCard,
UserAuthCardProps,
} from "../react-components"
export const UserAuthCard = (props: UserAuthCardProps) => {
return ComponentWrapper(userAuthCard(props))
}
export type { UserAuthCardProps } from "../react-components"
Assets are bundled into a singular style.css
file under each packages' dist/
folder. Anything placed inside the
assets folder will be
bundled. They can also be directly imported by the React components to be used
and are sometimes required by a component. An example is the
Social Button Component.
Ory Elements uses a fully automatic release publishing pipeline. All that is necessary is to create a new release on GitHub, after which the workflow runs all the necessary steps to release the modules to the NPM registry.
Most of the time, new features to this repository need some work in the corresponding Ory products to work. To make the development cycle more productive, it's possible to generate the SDK from a local OpenAPI / Swagger spec file.
export KRATOS_DIR=/path/to/kratos # point this variable to the root of your local Kratos clone
make build-sdk
This copies the current OpenAPI spec from the local Kratos repository to the
current Elements directory (./contrib/sdk/api.json
).
After that, it generates the Typescript SDK according to the spec and copies it
to the node_modules
directory. This overrides the currently installed module!
Now you can use the updated SDK without publishing to NPM first.
To test local changes in @ory/elements
in a local Ory examples repository, you
can point NPM to use a local directory instead of a remote package from the
registry.
This requires to first build @ory/elements
:
# In your cloned elements directory
npm run build # or more specialized `npm run build:react, etc.`
Make sure, that the build passed without errors!
After that, you can set the path to elements in the package.json
of your
project:
npm i /path/to/elements/packages/markup
# or for preact
# npm i /path/to/elements/packages/preact
# or for react
# npm i /path/to/elements/packages/react
Make sure to not commit these changes, as they will break on CI or on other machines that have a different setup.