UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

804 lines (714 loc) 19.7 kB
--- title: 'Space' description: 'The Space component provides margins within the provided spacing patterns.' version: 11.0.0 generatedAt: 2026-04-21T13:57:53.930Z checksum: 090b7d977ba4be5e2c4c04d199a30a4048416c59f443a56985df2f80629d9c40 --- # Space ## Import ```tsx import { Space } from '@dnb/eufemia' ``` ## Description The Space component provides `margins` within the [provided spacing patterns](/uilib/usage/layout/spacing#spacing-helpers). ## Relevant links - [Source code](https://github.com/dnbexperience/eufemia/tree/main/packages/dnb-eufemia/src/components/space) - [Docs code](https://github.com/dnbexperience/eufemia/tree/main/packages/dnb-design-system-portal/src/docs/uilib/layout/space) The reason this exists is to make your syntax as clean as possible. This way, you see directly in words what the spacing is for every affected component. ### Spacing Table | Pixel | Type | Rem | Custom Property | | ----- | ---------- | ------- | -------------------- | | 8 | `x-small` | **0.5** | `--spacing-x-small` | | 16 | `small` | **1** | `--spacing-small` | | 24 | `medium` | **1.5** | `--spacing-medium` | | 32 | `large` | **2** | `--spacing-large` | | 48 | `x-large` | **3** | `--spacing-x-large` | | 56 | `xx-large` | **3.5** | `--spacing-xx-large` | **NB:** In some circumstances you may be in need of using **0.25rem** (4px) - therefore `xx-small` also exists, but as a single type. So, combining `xx-small` and `small` would not result in 0.25rem, but still remain 1rem. ### Value Format There are a couple of different ways you can define the spacing types and values: - **Types:** `small small x-small` (combine types up to _10rem_) - **number:** `2.5` (equivalent to `rem`) - **string(rem):** `2.5rem` - **string(px):** `40px` (gets converted to `rem`) - **boolean:** `true` (equivalent to `small`), `false` (equivalent to `zero`) To get a spacing of e.g. **2.5rem** (40px), you may combine types `large` and `x-small`. ```tsx { /* All of these methods will result in the same spacing */ } ;<Space top="large x-small" right="2.5" bottom="2.5rem" left="40px" /> ``` With React, you can also use an object with the different directions: ```tsx { /* All of these methods will result in the same spacing */ } ;<Space space={{ top: 'large x-small', right: '2.5', bottom: '2.5rem', left: '40px', }} /> ``` ### Components and Spacing Every component supports the spacing patterns, so it's possible to send in the `top`, `right`, `bottom`, `left` and `space` properties directly, like: ```tsx <Button top="large x-small medium" /> <Button space={{ top: 'large x-small medium', }} /> ``` ### Spacing shorthands A shorthand for getting 1rem (most used) is to simply send in a boolean set as true. No given value in JSX means true, so you only need the property key: ```tsx { /* Equivalent to top="small" */ } ;<Button top /> { /* Equivalent to top="small" right="small" bottom="small" left="small" */ } ;<Button space /> ``` In order to set all four directions at once, you can provide a string as the `space` value: ```tsx render(<Button space="large x-small medium" />) ``` ### Does it not work as expected? Is `margin` not giving the expected spacing? That may be due to **Margin Collapsing**. Margins collapse in the following situations: - Adjacent siblings - Completely empty boxes - Parent and first or last child element The best solution is to only use one direction of margins, e.g. `bottom`. Or you can set the [collapse property](/uilib/layout/space/properties) to `false`. ### Margin collapsing In order to help handle unwanted margin collapsing in typography elements, see [this example](/uilib/elements/heading#example-of-margin-collapsing). ### Conditional Reset For resetting spacing (`margin: 0`) only when no spacing is defined, you can make use of `dnb-space__reset`. ### Style and Spacing Every Eufemia component that supports spacing props uses CSS custom properties (e.g. `--margin-t-s`) on the `style` attribute to drive responsive margins. When you pass a `style` prop to a component, your styles and the spacing styles are merged together — spacing properties take precedence. This means you can safely combine your own styles with spacing: ```tsx <Space style={{ color: 'var(--color-sea-green)' }} top="medium"> ... </Space> ``` If you work with raw DOM elements and set styles via `setAttribute('style', ...)`, make sure you preserve any existing style values when adding new ones, so the spacing custom properties are not lost. ```js const existing = element.getAttribute('style') const merged = existing ? `${existing.replace(/;?\s*$/, '')}; ${style}` : style element.setAttribute('style', merged) ``` ## Demos ### Spacing method #1 `Space` component. The RedBox is only to visualize the result. ```tsx render( <TestStyles> <ComponentBox data-visual-test="spacing-method-space" scope={{ RedBox, }} > <RedBox> <Space top="large x-small"> <Input label="Input" /> </Space> </RedBox> </ComponentBox> </TestStyles> ) ``` ### Spacing method #2 Define the space directly. ```tsx render( <TestStyles> <ComponentBox data-visual-test="spacing-method-component"> <Input label="Input A" bottom="small" /> <Input label="Input B" /> </ComponentBox> </TestStyles> ) ``` ### Spacing method #3 Using `createSpacing` or `applySpacing`. ```tsx render( <TestStyles> <ComponentBox scope={{ RedBox, applySpacing, }} data-visual-test="spacing-method-form-row" > {() => { const Component = ({ className = null, style = null, ...props }) => { const params = applySpacing(props, { ...props, className: `my-component dnb-space ${className || ''}`.trim(), style, }) return <div {...params} /> } return ( <> <RedBox> <Component top="small medium large">Space A</Component> </RedBox> <RedBox> <Component top>Space B</Component> </RedBox> <RedBox> <Component innerSpace="large">Inner Space</Component> </RedBox> <RedBox> <Component innerSpace={{ large: true, }} > Has space when breakpoint is large </Component> </RedBox> </> ) }} </ComponentBox> </TestStyles> ) ``` ## Responsive `space` The `space` property supports [media query breakpoints](/uilib/usage/layout/media-queries) (`small`, `medium`, `large`) for responsive spacing. Provide an object with breakpoint keys to apply different values at each screen size. ```tsx render( <TestStyles> <ComponentBox data-visual-test="responsive-outer-spacing" scope={{ RedBox, }} > <RedBox> <Space space={{ small: 'large x-small', medium: { top: '2rem', left: '16px', bottom: 'large', right: '5rem', }, large: true, }} > <P>Content</P> </Space> </RedBox> </ComponentBox> </TestStyles> ) ``` ### Responsive `innerSpace` The `innerSpace` property controls padding inside the Space component. It shares the same API as `space`. ```tsx render( <TestStyles> <ComponentBox data-visual-test="inner-spacing" scope={{ RedBox, }} > <RedBox> <Space innerSpace={{ small: 'large x-small', medium: true, large: { top: '2rem', left: '16px', bottom: 'large', right: '5rem', }, }} > <P>Content</P> </Space> </RedBox> </ComponentBox> </TestStyles> ) ``` ### `inline` and `block` shorthand Both `space` and `innerSpace` properties support `inline` and `block` shorthand properties for more semantic spacing control. - `inline` applies spacing to left and right (horizontal) - `block` applies spacing to top and bottom (vertical) ```tsx render( <TestStyles> <ComponentBox data-visual-test="space-inline-block" scope={{ RedBox, }} > {/* Basic inline/block usage for space (margin) */} <Space space={{ inline: 'small', block: 'large', }} > <RedBox> space: inline=small (left/right), block=large (top/bottom) </RedBox> </Space> {/* Basic inline/block usage for innerSpace (padding) */} <Space innerSpace={{ inline: 'medium', block: 'x-small', }} > <RedBox> innerSpace: inline=medium (left/right), block=x-small (top/bottom) </RedBox> </Space> {/* Combining both space and innerSpace with inline/block */} <Space space={{ block: 'large', }} innerSpace={{ inline: 'medium', block: 'small', }} > <RedBox> Combined: space block=large + innerSpace inline=medium, block=small </RedBox> </Space> {/* Media queries with inline/block for both properties */} <Space space={{ small: { inline: 'x-small', }, medium: { block: 'medium', }, large: { inline: 'large', block: 'small', }, }} innerSpace={{ small: { block: 'x-small', }, medium: { inline: 'small', }, large: { inline: 'medium', block: 'large', }, }} > <RedBox> <div>Responsive inline/block for both space and innerSpace</div> <div>Different combinations per breakpoint</div> </RedBox> </Space> {/* Mixing inline/block with traditional directional props */} <Space space={{ inline: 'small', }} top="x-large" innerSpace={{ block: 'medium', }} > <RedBox> Mixed: space inline + top override, innerSpace block </RedBox> </Space> </ComponentBox> </TestStyles> ) ``` ### Spacing with no margin collapse, due to the flex usage ```tsx render( <TestStyles> <ComponentBox hideCode scope={{ RedBox, Vertical, }} > <Vertical> <RedBox> <Space bottom="small"> <> I have <code className="dnb-code">bottom="small"</code> </> </Space> </RedBox> <RedBox> <Space top="large"> <> I have <code className="dnb-code">top="large"</code> </> </Space> </RedBox> </Vertical> </ComponentBox> </TestStyles> ) ``` ### All four values will result in an equivalent margin ```tsx render( <TestStyles> <ComponentBox data-visual-test="spacing-margins" hideCode> <Space top="large x-small" right="2.5" bottom="2.5rem" left="40px"> <details> <summary> I have four <code className="dnb-code">2.5rem</code> margins! </summary> And this are my CSS classes:{' '} <code className="dnb-code"> dnb-space dnb-space__top--large dnb-space__top--x-small dnb-space__right--large dnb-space__right--x-small dnb-space__bottom--large dnb-space__bottom--x-small dnb-space__left--large dnb-space__left--x-small </code> </details> </Space> </ComponentBox> </TestStyles> ) ``` ### Visual space testing ```tsx render( <TestStyles> <ComponentBox data-visual-test="spacing-patterns" scope={{ MagicBox, CustomStyle, }} hideCode > {() => { const TestCase = (props) => { return ( <CustomStyle {...props}> {listOfBoxes.map((v) => ( <Space key={v} top={v}> <MagicBox /> </Space> ))} </CustomStyle> ) } const listOfBoxes = [] for (let i = 0, c = 0, l = 20; i <= l; i++) { listOfBoxes.push(String(c)) c += 0.5 } return ( <div className="spacing-patterns"> <P bottom> With <Code>dnb-core-style</Code> </P> <TestCase className="dnb-core-style" /> <P top bottom> Without </P> <TestCase /> </div> ) }} </ComponentBox> </TestStyles> ) ``` ```tsx render( <TestStyles> <ComponentBox data-visual-test="spacing-elements" scope={{ MagicBox, CustomStyle, }} hideCode > {() => { const listOfBoxes = [] for (let i = 0, c = 0, l = 10; i <= l; i++) { listOfBoxes.push(String(c)) c += 1 } const TestCase = (props) => { return ( <CustomStyle {...props}> {listOfBoxes.map((v) => ( <Button key={v} left="x-small" top={v} size="small" customContent={<MagicBox />} /> ))} </CustomStyle> ) } return ( <div className="spacing-elements"> <P bottom> With <Code>dnb-core-style</Code> </P> <TestCase className="dnb-core-style" /> <P top bottom> Without </P> <TestCase /> </div> ) }} </ComponentBox> </TestStyles> ) ``` ```tsx const BlueBox = styled.div` display: inline-block; padding: 0.5rem; background: blue; ul { background: white; } ` render( <BlueBox> <Space element="ul" top="small" right="small" bottom="small" left="small" className="dnb-space__reset" > <li> </li> </Space> </BlueBox> ) ``` ```tsx render( <TestStyles> <ComponentBox data-visual-test="space-media-queries" scope={{ RedBox, }} > {/* Different spacing for different breakpoints */} <Space space={{ small: 'small', medium: 'large', large: 'x-large', }} > <RedBox> Responsive spacing: small on mobile, large on tablet, x-large on desktop </RedBox> </Space> {/* Media queries with individual direction objects */} <Space space={{ small: { top: 'small', bottom: 'medium', }, medium: { top: 'large', bottom: 'x-large', }, large: { top: 'x-large', bottom: 'xx-large', }, }} > <RedBox>Responsive directional spacing</RedBox> </Space> {/* Mixing with individual props */} <Space space={{ small: 'medium', medium: 'large', large: 'x-large', }} right="small" // Individual props override space > <RedBox>Media space with right override</RedBox> </Space> </ComponentBox> </TestStyles> ) ``` ```tsx render( <TestStyles> <ComponentBox data-visual-test="innerspace-media-queries" scope={{ RedBox, }} > {/* Different inner spacing for different breakpoints */} <Space innerSpace={{ small: 'small', medium: 'large', large: 'x-large', }} > <RedBox> <div>Responsive inner spacing</div> <div>Content inside has different spacing per breakpoint</div> </RedBox> </Space> {/* Media queries with directional inner spacing */} <Space innerSpace={{ small: { block: 'small', inline: 'medium', }, medium: { block: 'large', inline: 'x-large', }, large: { block: 'x-large', inline: 'xx-large', }, }} > <RedBox> <div>Responsive directional inner spacing</div> <div>Block and inline spacing changes per breakpoint</div> </RedBox> </Space> </ComponentBox> </TestStyles> ) ``` ## Global Properties These properties are available in many other components and elements. ```json { "props": { "space": { "doc": "Has to be an object with either: `top`, `right`, `bottom`, `left`, `inline`, or `block`. Also supports media query breakpoints like `{small: \"medium\", medium: \"large\", large: \"x-large\"}` and shorthand directions `inline`/`block`. Use spacing values like: `small`, `1rem`, `1` or `16px`.", "type": ["object"], "status": "optional" }, "top": { "doc": "Use spacing values like: `small`, `1rem`, `1` or `16px`. Will use `margin-top`.", "type": ["string", "number", "boolean"], "status": "optional" }, "right": { "doc": "Use spacing values like: `small`, `1rem`, `1` or `16px`. Will use `margin-right`.", "type": ["string", "number", "boolean"], "status": "optional" }, "bottom": { "doc": "Use spacing values like: `small`, `1rem`, `1` or `16px`. Will use `margin-bottom`.", "type": ["string", "number", "boolean"], "status": "optional" }, "left": { "doc": "Use spacing values like: `small`, `1rem`, `1` or `16px`. Will use `margin-left`.", "type": ["string", "number", "boolean"], "status": "optional" } } } ``` ## Component Properties ```json { "props": { "element": { "doc": "Defines the HTML element used. Defaults to `div`.", "type": ["string", "React.Element"], "status": "optional" }, "stretch": { "doc": "If set to `true`, then the space element will be 100% in width.", "type": "boolean", "status": "optional" }, "inline": { "doc": "If set to `true`, then `display: inline-block;` is used, so the HTML elements get aligned horizontally. Defaults to `false`.", "type": "boolean", "status": "optional" }, "innerSpace": { "doc": "Will add a padding around the content. Supports also media query breakpoints like `{small: { top: 'medium' }}` and shorthand directions `inline`/`block`.", "type": ["object", "string", "number", "boolean"], "status": "optional" }, "noCollapse": { "doc": "If set to `true`, then a wrapper with `display: flow-root;` is used. This way you avoid **Margin Collapsing**. Defaults to `false`. _Note:_ You can't use `inline={true}` in combination.", "type": "boolean", "status": "optional" } } } ``` ## Zero Use either `0` or `false` (as a number/boolean or string) to set a `margin` of 0. ## Provider Also, Provider is supporting the `collapse` property. ```tsx render( <Provider space={{ noCollapse: true, }} > <Space>I do not collapse</Space> <Space>I do not collapse</Space> </Provider> ) ```