diff --git a/docs-ui/src/app/components/page.mdx b/docs-ui/src/app/components/page.mdx index bd18005d19..399d9aafbf 100644 --- a/docs-ui/src/app/components/page.mdx +++ b/docs-ui/src/app/components/page.mdx @@ -1,173 +1,9 @@ -import { ComponentCards, ComponentCard } from '@/components/ComponentCards'; -import { LayoutComponents } from '@/components/LayoutComponents'; -import { CodeBlock } from '@/components/CodeBlock'; +import { ComponentGrid } from '@/components/ComponentGrid'; # Components -## Layout Components +Below is the full list of components available in the library. Each component is designed to be flexible, accessible, +and easy to integrate into your plugins. We are actively working on adding more components, so check back regularly +for updates. -We built a couple of layout components to help you build responsive elements -that will be consistent with the rest of your Backstage instance. These -components are opinionated and use TypeScript to ensure that the props you -provide are the ones coming from the theme. - - - Hello World - - Project 1 - Project 2 - - -`} -/> - - - -## Components - -### Actions - - - - - - - - -### Content display - - - - - - -### Selection and inputs - - - - - - - - - - - -### Navigation - - - - - - - - -### Images and icons - - - - - - -### Feedback indicators - - - - - - -### Typography - - - - + diff --git a/docs-ui/src/app/get-started/installation/page.mdx b/docs-ui/src/app/get-started/installation/page.mdx new file mode 100644 index 0000000000..cdb14d5924 --- /dev/null +++ b/docs-ui/src/app/get-started/installation/page.mdx @@ -0,0 +1,46 @@ +import { CodeBlock } from '@/components/CodeBlock'; +import { Banner } from '@/components/Banner'; +import { snippet } from './snippets'; + +# Installation + +## Import BUI's global styles + +Backstage UI works by importing a global CSS file at the root of your application. This file includes all the default styles for the components. +First, you'll need to install the package using a package manager. For example, if you're using Yarn: + + + +);`} +/> + + + +## Use BUI components + +As a plugin maintainer, you can use BUI components in your plugin. As mentioned above, you should not import the styles +again in your plugin as this will be handled at the root of your application. To get started, just add the library to +your plugin and import the components you need. + + + + diff --git a/docs-ui/src/app/get-started/installation/snippets.ts b/docs-ui/src/app/get-started/installation/snippets.ts new file mode 100644 index 0000000000..7e63005172 --- /dev/null +++ b/docs-ui/src/app/get-started/installation/snippets.ts @@ -0,0 +1,6 @@ +export const snippet = `import { Flex, Button, Text } from '@backstage/ui'; + + + Hello World + +;`; diff --git a/docs-ui/src/app/page.mdx b/docs-ui/src/app/page.mdx index 9a796b401b..712a5ac5c6 100644 --- a/docs-ui/src/app/page.mdx +++ b/docs-ui/src/app/page.mdx @@ -1,59 +1,78 @@ +import { ComponentGrid } from '@/components/ComponentGrid'; import { CodeBlock } from '@/components/CodeBlock'; -import { Banner } from '@/components/Banner'; -import { snippet } from './snippets'; +import { ColorFamily } from '@/components/ColorFamily'; +import { + surfacesSnippet, + adaptiveSnippet, + customCardSnippet, + customTokensSnippet, + colorPickerSnippet, +} from './snippets'; -# Welcome to Backstage UI +# Get Started with BUI Backstage UI is a design system created specifically for Backstage, built with React, TypeScript, and vanilla CSS. This open-source library is hosted in the Backstage monorepo. While it can be used in other projects, Backstage UI is designed to deliver a consistent, accessible, and extensible experience tailored to Backstage users. -## Import BUI's global styles +Backstage UI is installed by default on every instance of Backstage, so you can start using it right away. +If your setup doesn't include it yet, follow the [installation guide](/get-started/installation) to get started. -Backstage UI works by importing a global CSS file at the root of your application. This file includes all the default styles for the components. -First, you'll need to install the package using a package manager. For example, if you're using Yarn: +## Layout containers - +[`Box`](/components/box), [`Flex`](/components/flex), [`Grid`](/components/grid), and [`Card`](/components/card) are the foundation of every layout in Backstage UI. +Each one offers a set of utility props that map directly to our design tokens, so you can build consistent +layouts without writing any CSS. When nested, they also act as surfaces and automatically increment the +background depth so visual hierarchy is handled for you. );`} + title="Nested surfaces with automatic styling" + code={surfacesSnippet} /> - +## Adaptive components -## Use BUI components - -As a plugin maintainer, you can use BUI components in your plugin. As mentioned above, you should not import the styles -again in your plugin as this will be handled at the root of your application. To get started, just add the library to -your plugin and import the components you need. +Components like [`Card`](/components/card), [`Button`](/components/button), [`Text`](/components/text), and others are **adaptive components**. They +automatically adjust their colors, borders, and backgrounds to match the surface they live on. Drop a +[`Button`](/components/button) inside a [`Card`](/components/card) inside a [`Box`](/components/box) and each component styles itself appropriately +without any extra configuration. - +## The neutral scale background colors -## Support + -Now that you have the basics down, you can start building your plugin using the new design system. -Please familiarise yourself first with our theming principles. This will help you understand the core concepts of the design system. -If you have any questions, please reach out to us on [Discord](https://discord.gg/MUpMjP2). +## Creating custom components + +As much as possible we would like you to use components directly without creating custom components. If you need to create a custom component, you should use the components provided by Backstage UI. + + + +If you need to build custom components outside of BUI, you can use our [design tokens](/tokens) as CSS variables to ensure your styles stay consistent with the rest of the system. + + + +When building custom interactive components, we strongly recommend using [React Aria](https://react-spectrum.adobe.com/react-aria/) as your foundation. React Aria provides all the necessary accessibility features out of the box — keyboard navigation, focus management, ARIA attributes, and screen reader support — so you can focus on styling and logic without worrying about compliance. + + ## Philosophy diff --git a/docs-ui/src/app/snippets.ts b/docs-ui/src/app/snippets.ts index 7e63005172..e16463a085 100644 --- a/docs-ui/src/app/snippets.ts +++ b/docs-ui/src/app/snippets.ts @@ -1,6 +1,39 @@ -export const snippet = `import { Flex, Button, Text } from '@backstage/ui'; +export const surfacesSnippet = ` + + + + + + +`; - +export const adaptiveSnippet = ` + {/* automatically set background to neutral-2 */} + + +`; + +export const customCardSnippet = ` Hello World - -;`; +`; + +export const customTokensSnippet = `
+
Hello World
+
`; + +export const colorPickerSnippet = `import { ColorPicker, ColorArea, ColorSlider, ColorField } from 'react-aria-components'; + +function MyColorPicker() { + return ( + + + + + + ); +}`; diff --git a/docs-ui/src/components/ColorFamily/ColorFamily.module.css b/docs-ui/src/components/ColorFamily/ColorFamily.module.css new file mode 100644 index 0000000000..f38e481383 --- /dev/null +++ b/docs-ui/src/components/ColorFamily/ColorFamily.module.css @@ -0,0 +1,94 @@ +.wrapper { + display: flex; + flex-direction: column; + gap: 2rem; + margin-top: 1rem; + margin-bottom: 2rem; +} + +@media (min-width: 768px) { + .wrapper { + flex-direction: row; + align-items: flex-start; + } +} + +.visual { + flex: 1; + min-width: 0; +} + +.text { + flex: 1; + min-width: 0; +} + +.base { + border-radius: 12px; + padding: 16px; +} + +.baseLabel { + display: block; + font-size: 13px; + color: var(--bui-fg-primary); + margin-bottom: 12px; +} + +.level { + border-radius: 10px; + padding: 12px; +} + +.levelHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.level > .level > .levelHeader:last-child, +.level:not(:has(> .level)) > .levelHeader { + margin-bottom: 0; +} + +.levelLabel { + font-size: 13px; + color: var(--bui-fg-primary); + white-space: nowrap; +} + +.chips { + display: flex; + gap: 6px; +} + +.chip { + padding: 6px 10px; + border-radius: 6px; + font-size: 11px; + font-weight: 500; + color: var(--bui-fg-primary); + white-space: nowrap; +} + +.description { + color: var(--primary); + margin-top: 0; + margin-bottom: 1rem; + font-size: 1rem; + line-height: 1.5; +} + +.description:last-child { + margin-bottom: 0; +} + +.link { + color: var(--link); + text-decoration: none; + + &:hover { + text-decoration: underline; + } +} diff --git a/docs-ui/src/components/ColorFamily/ColorFamily.tsx b/docs-ui/src/components/ColorFamily/ColorFamily.tsx new file mode 100644 index 0000000000..03cf8be848 --- /dev/null +++ b/docs-ui/src/components/ColorFamily/ColorFamily.tsx @@ -0,0 +1,95 @@ +'use client'; + +import styles from './ColorFamily.module.css'; + +const levels = [1, 2, 3, 4] as const; + +const StateChip = ({ + level, + state, + label, +}: { + level: number; + state: string; + label: string; +}) => ( +
+ {label} +
+); + +const NeutralLevel = ({ + level, + children, + height, +}: { + level: number; + children?: React.ReactNode; + height?: number; +}) => ( +
+
+ Neutral {level} +
+ + + +
+
+ {children} +
+); + +export const ColorFamily = () => { + return ( +
+
+
+ Neutral 0 + + + + + + + +
+
+
+

+ BUI uses a layered neutral scale from 0 to 4. Each level nests inside + the previous one, automatically incrementing the background depth. + This creates clear visual hierarchy without manually picking colors. +

+

+ Each level can be interactive or{' '} + non-interactive. A Card, for example, can be flat + (just a surface) or fully clickable with hover and pressed states. The + three interaction states (hover, pressed, disabled) are built into + every neutral level. +

+

+ Explore the full list of color tokens on the{' '} + + tokens page + + . +

+
+
+ ); +}; diff --git a/docs-ui/src/components/ColorFamily/index.ts b/docs-ui/src/components/ColorFamily/index.ts new file mode 100644 index 0000000000..12f7fb4008 --- /dev/null +++ b/docs-ui/src/components/ColorFamily/index.ts @@ -0,0 +1 @@ +export { ColorFamily } from './ColorFamily'; diff --git a/docs-ui/src/components/ComponentCards/ComponentCards.module.css b/docs-ui/src/components/ComponentCards/ComponentCards.module.css deleted file mode 100644 index 8eb9dbd3d0..0000000000 --- a/docs-ui/src/components/ComponentCards/ComponentCards.module.css +++ /dev/null @@ -1,54 +0,0 @@ -.grid { - display: grid; - grid-template-columns: repeat(1, 1fr); - gap: 1rem; - margin-top: 1rem; - margin-bottom: 3rem; -} - -@media (min-width: 768px) { - .grid { - grid-template-columns: repeat(2, 1fr); - } -} - -@media (min-width: 1024px) { - .grid { - grid-template-columns: repeat(3, 1fr); - } -} - -@media (min-width: 1280px) { - .grid { - grid-template-columns: repeat(4, 1fr); - } -} - -.card { - display: flex; - flex-direction: column; - justify-content: flex-end; - background-color: var(--bg); - border-radius: 8px; - border: 1px solid var(--border); - padding: 16px; - gap: 4px; - min-height: 120px; - - &:hover { - background-color: var(--bg-hover); - } -} - -.title { - margin: 0; - font-size: 16px; - font-weight: 600; - color: var(--primary); -} - -.description { - margin: 0; - font-size: 14px; - color: var(--secondary); -} diff --git a/docs-ui/src/components/ComponentCards/ComponentCards.tsx b/docs-ui/src/components/ComponentCards/ComponentCards.tsx deleted file mode 100644 index 0e4d22c02a..0000000000 --- a/docs-ui/src/components/ComponentCards/ComponentCards.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Link from 'next/link'; -import styles from './ComponentCards.module.css'; - -export const ComponentCards = ({ children }: { children: React.ReactNode }) => { - return
{children}
; -}; - -export const ComponentCard = ({ - title, - description, - href, -}: { - title: string; - description: string; - href: string; -}) => { - return ( - -

{title}

-

{description}

- - ); -}; diff --git a/docs-ui/src/components/ComponentCards/index.ts b/docs-ui/src/components/ComponentCards/index.ts deleted file mode 100644 index 4bc4beaae3..0000000000 --- a/docs-ui/src/components/ComponentCards/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ComponentCards'; diff --git a/docs-ui/src/components/ComponentGrid/ComponentGrid.module.css b/docs-ui/src/components/ComponentGrid/ComponentGrid.module.css new file mode 100644 index 0000000000..f98e4f0b93 --- /dev/null +++ b/docs-ui/src/components/ComponentGrid/ComponentGrid.module.css @@ -0,0 +1,37 @@ +.grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + margin-top: 1rem; + margin-bottom: 2rem; +} + +@media (min-width: 768px) { + .grid { + grid-template-columns: repeat(4, 1fr); + } +} + +.item { + display: flex; + align-items: center; + gap: 6px; + padding: 10px 0; + font-size: 14px; + font-weight: 400; + color: var(--primary); + text-decoration: none; + + &:hover { + color: var(--link); + text-decoration: underline; + text-underline-offset: 2px; + } +} + +.dot { + width: 6px; + height: 6px; + border-radius: 50%; + background-color: var(--link); + flex-shrink: 0; +} diff --git a/docs-ui/src/components/ComponentGrid/ComponentGrid.tsx b/docs-ui/src/components/ComponentGrid/ComponentGrid.tsx new file mode 100644 index 0000000000..4d7408b829 --- /dev/null +++ b/docs-ui/src/components/ComponentGrid/ComponentGrid.tsx @@ -0,0 +1,22 @@ +'use client'; + +import Link from 'next/link'; +import { components } from '@/utils/data'; +import styles from './ComponentGrid.module.css'; + +export const ComponentGrid = () => { + return ( +
+ {components.map(item => ( + + {item.title} + {item.status === 'new' && } + + ))} +
+ ); +}; diff --git a/docs-ui/src/components/ComponentGrid/index.ts b/docs-ui/src/components/ComponentGrid/index.ts new file mode 100644 index 0000000000..f0fe06477a --- /dev/null +++ b/docs-ui/src/components/ComponentGrid/index.ts @@ -0,0 +1 @@ +export * from './ComponentGrid'; diff --git a/docs-ui/src/components/LayoutComponents/LayoutComponents.module.css b/docs-ui/src/components/LayoutComponents/LayoutComponents.module.css deleted file mode 100644 index 9009cdb6a4..0000000000 --- a/docs-ui/src/components/LayoutComponents/LayoutComponents.module.css +++ /dev/null @@ -1,48 +0,0 @@ -.layoutComponents { - display: flex; - justify-content: flex-start; - gap: 1rem; - flex-wrap: wrap; - margin-top: 2rem; - - & svg rect { - transition: fill 0.2s ease-in-out; - } - - & .box { - display: flex; - flex-direction: column; - width: calc(50% - 0.5rem); - margin-bottom: 1rem; - align-items: flex-start; - } - - & .content { - flex: none; - background-color: var(--bg); - border: 1px solid var(--border); - border-radius: 4px; - width: 100%; - height: 180px; - transition: all 0.2s ease-in-out; - margin-bottom: 0.75rem; - display: flex; - align-items: center; - justify-content: center; - - &:hover { - transform: translateY(-4px); - } - } - - & .title { - font-size: 16px; - transition: color 0.2s ease-in-out; - margin-bottom: 0.25rem; - } - - & .description { - font-size: 16px; - color: var(--secondary); - } -} diff --git a/docs-ui/src/components/LayoutComponents/LayoutComponents.tsx b/docs-ui/src/components/LayoutComponents/LayoutComponents.tsx deleted file mode 100644 index 0bf822526e..0000000000 --- a/docs-ui/src/components/LayoutComponents/LayoutComponents.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { BoxSvg } from './svgs/box'; -import { FlexSvg } from './svgs/flex'; -import { GridSvg } from './svgs/grid'; -import { ContainerSvg } from './svgs/container'; -import styles from './LayoutComponents.module.css'; -import Link from 'next/link'; - -export const LayoutComponents = () => { - return ( -
-
- - - -
Box
-
- The most basic layout component -
-
-
- - - -
Flex
-
- Arrange your components vertically -
-
-
- - - -
Grid
-
- Arrange your components in a grid -
-
-
- - - -
Container
-
- A container for your components -
-
-
- ); -}; diff --git a/docs-ui/src/components/LayoutComponents/index.ts b/docs-ui/src/components/LayoutComponents/index.ts deleted file mode 100644 index 8efb793bbd..0000000000 --- a/docs-ui/src/components/LayoutComponents/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { LayoutComponents } from './LayoutComponents'; diff --git a/docs-ui/src/components/Navigation/Navigation.module.css b/docs-ui/src/components/Navigation/Navigation.module.css index f5e1312ab1..efba109f97 100644 --- a/docs-ui/src/components/Navigation/Navigation.module.css +++ b/docs-ui/src/components/Navigation/Navigation.module.css @@ -49,16 +49,13 @@ } .sectionTitle { - font-size: 0.6875rem; - font-weight: 500; - padding: 0 12px 4px; - color: var(--secondary); + display: flex; + align-items: center; + gap: 8px; + color: var(--primary); margin-top: 40px; - text-transform: uppercase; - - &:first-child { - margin-top: 12px; - } + padding: 8px 12px; + margin-bottom: 8px; } .line { diff --git a/docs-ui/src/components/Navigation/Navigation.tsx b/docs-ui/src/components/Navigation/Navigation.tsx index 13fa87661f..bad77e942e 100644 --- a/docs-ui/src/components/Navigation/Navigation.tsx +++ b/docs-ui/src/components/Navigation/Navigation.tsx @@ -2,7 +2,6 @@ import Link from 'next/link'; import { usePathname } from 'next/navigation'; -import { Fragment } from 'react'; import clsx from 'clsx'; import { RiCollageLine, @@ -12,26 +11,13 @@ import { RiServiceLine, RiStackLine, } from '@remixicon/react'; -import { components, layoutComponents } from '@/utils/data'; +import { components } from '@/utils/data'; import styles from './Navigation.module.css'; interface NavigationProps { onLinkClick?: () => void; } -const data = [ - { - title: 'Layout Components', - content: layoutComponents, - url: '/components', - }, - { - title: 'Components', - content: components, - url: '/components', - }, -]; - export const Navigation = ({ onLinkClick }: NavigationProps) => { const pathname = usePathname(); @@ -55,16 +41,6 @@ export const Navigation = ({ onLinkClick }: NavigationProps) => { Tokens -
  • - - - Components - -
  • @@ -89,35 +65,31 @@ export const Navigation = ({ onLinkClick }: NavigationProps) => {
  • - {data.map(section => { +
    + + Components +
    + {components.map(item => { + const isActive = pathname === `/components/${item.slug}`; + return ( - -
    {section.title}
    - - {section.content.map(item => { - const isActive = pathname === `${section.url}/${item.slug}`; - - return ( - -
    {item.title}
    -
    - {item.status === 'alpha' && 'Alpha'} - {item.status === 'beta' && 'Beta'} - {item.status === 'inProgress' && 'In Progress'} - {item.status === 'stable' && 'Stable'} - {item.status === 'deprecated' && 'Deprecated'} -
    - - ); + + onClick={onLinkClick} + > +
    {item.title}
    +
    + {item.status === 'alpha' && 'Alpha'} + {item.status === 'beta' && 'Beta'} + {item.status === 'inProgress' && 'In Progress'} + {item.status === 'stable' && 'Stable'} + {item.status === 'deprecated' && 'Deprecated'} +
    + ); })} diff --git a/docs-ui/src/components/Toolbar/Toolbar.tsx b/docs-ui/src/components/Toolbar/Toolbar.tsx index e7defd5e8a..05757c4459 100644 --- a/docs-ui/src/components/Toolbar/Toolbar.tsx +++ b/docs-ui/src/components/Toolbar/Toolbar.tsx @@ -21,7 +21,7 @@ import styles from './Toolbar.module.css'; import { usePlayground } from '@/utils/playground-context'; import { usePathname } from 'next/navigation'; import Link from 'next/link'; -import { components, layoutComponents } from '@/utils/data'; +import { components } from '@/utils/data'; import { Logo } from '@/components/Sidebar/Logo'; interface ToolbarProps { @@ -46,12 +46,7 @@ export const Toolbar = ({ version }: ToolbarProps) => { // Determine breadcrumb content based on current path const getBreadcrumb = () => { - const allComponents = [...components, ...layoutComponents]; - - // Root page - if (pathname === '/') { - return { section: null, title: 'Getting Started' }; - } + const allComponents = components; // Components index page if (pathname === '/components') { diff --git a/docs-ui/src/utils/data.ts b/docs-ui/src/utils/data.ts index e2a3e411a4..7426b16130 100644 --- a/docs-ui/src/utils/data.ts +++ b/docs-ui/src/utils/data.ts @@ -4,25 +4,6 @@ interface Page { status?: 'alpha' | 'beta' | 'stable' | 'deprecated' | 'inProgress' | 'new'; } -export const layoutComponents: Page[] = [ - { - title: 'Box', - slug: 'box', - }, - { - title: 'Container', - slug: 'container', - }, - { - title: 'Grid', - slug: 'grid', - }, - { - title: 'Flex', - slug: 'flex', - }, -]; - export const components: Page[] = [ { title: 'Accordion', @@ -36,6 +17,10 @@ export const components: Page[] = [ title: 'Avatar', slug: 'avatar', }, + { + title: 'Box', + slug: 'box', + }, { title: 'Button', slug: 'button', @@ -56,10 +41,22 @@ export const components: Page[] = [ title: 'Checkbox', slug: 'checkbox', }, + { + title: 'Container', + slug: 'container', + }, { title: 'Dialog', slug: 'dialog', }, + { + title: 'Flex', + slug: 'flex', + }, + { + title: 'Grid', + slug: 'grid', + }, { title: 'Header', slug: 'header', @@ -83,7 +80,6 @@ export const components: Page[] = [ { title: 'Popover', slug: 'popover', - status: 'new', }, { title: 'RadioGroup', diff --git a/docs-ui/src/utils/getPageName.ts b/docs-ui/src/utils/getPageName.ts index 7a1ccbf2a1..252c80c6c1 100644 --- a/docs-ui/src/utils/getPageName.ts +++ b/docs-ui/src/utils/getPageName.ts @@ -1,17 +1,10 @@ -import { components, layoutComponents } from './data'; +import { components } from './data'; export function getPageName(slug: string): string | null { - // Search in components array const component = components.find(c => c.slug === slug); if (component) { return component.title; } - // Search in layoutComponents array - const layoutComponent = layoutComponents.find(c => c.slug === slug); - if (layoutComponent) { - return layoutComponent.title; - } - return null; }