Surface

The base container the rest of the library's elevated components are built from — what other libraries call "paper".
A Surface owns exactly one decision: how far off the page it sits. level resolves background, border color and default shadow as a set from theme.surfaces, so a component can't end up with a level-3 shadow over a level-0 fill, and no component has to pick a background color itself.

Why a numeric ladder

Elevation is ordered and nestable, so the scale is numeric rather than a set of named slots (secondary, tertiary, …). Two things fall out of that:

Nesting composes. raised reads the enclosing Surface's level and adds one, so a popover opened inside a card lands a step above the card without either one knowing the other's number.

Scheme differences live in one place. Light mode conveys elevation mostly with shadow; dark mode can't — shadows are invisible on a near-black page — so it uses a progressively lighter fill plus a hairline border. Encoding that in the ladder means call sites never branch on colorScheme.

Theming

Override surfaces on your theme to retune every elevated component at once:

createTheme

(

{

  surfaces

:

 

{

    

0

:

 

{

background

:

 

'#0B0B0F'

,

border

:

 

'#1A1A20'

,

shadow

:

 

'none'

 

}

,

    

1

:

 

{

background

:

 

'#16161C'

,

border

:

 

'#26262E'

,

shadow

:

 

'xs'

 

}

,

    

2

:

 

{

background

:

 

'#1E1E26'

,

border

:

 

'#2E2E38'

,

shadow

:

 

'md'

 

}

,

    

3

:

 

{

background

:

 

'#26262F'

,

border

:

 

'#363642'

,

shadow

:

 

'xl'

 

}

,

  

}

,

}

)

Themes that don't define surfaces get a ladder derived from backgrounds (basesurfaceelevated), so existing custom themes keep working.

Used by

Card sits at level 1 (2 for variant="elevated"); Menu, Select dropdowns and Popover at level 2; Dialog at level 3. useSurfaceLevel() reports the level of the surface the caller is rendered on, and surfaceInteractionTint() returns hover/pressed/selected overlays that read correctly at any level.

Elevation levels

Each level resolves its own background, border and shadow from theme.surfaces. Toggle the color scheme to see the ladder switch from shadow-led to fill-led.

Loading demo…

import

 

{

 

Block

,

 

Surface

,

 

Text

 

}

 

from

 

'@platform-blocks/ui'

;

 

const

 

LEVELS

 

=

 

[

  

{

level

:

 

0

as

const

,

label

:

 

'Level 0 — the page'

 

}

,

  

{

level

:

 

1

as

const

,

label

:

 

'Level 1 — resting content'

 

}

,

  

{

level

:

 

2

as

const

,

label

:

 

'Level 2 — floating over content'

 

}

,

  

{

level

:

 

3

as

const

,

label

:

 

'Level 3 — takes over the screen'

 

}

,

]

;

 

export

 

default

 

function

 

Demo

(

)

 

{

  

return

 

(

    

<Block

fullWidth

>

      

{

LEVELS

.

map

(

(

{

level

,

label

}

)

 

=>

 

(

        

<Surface

 

key

=

{

level

}

 

level

=

{

level

}

 

padding

=

"md"

 

radius

=

"lg"

fullWidth

>

          

<Text

 

size

=

"sm"

>

{

label

}

</Text

>

        

</Surface

>

      

)

)

}

    

</Block

>

  

)

;

}

Nesting with raised

raised takes the enclosing Surface's level and adds one, so nested containers stack correctly without any of them hard-coding a number. Move the outer Surface to a different level and everything inside follows.

Loading demo…

import

 

{

 

Block

,

 

Surface

,

 

Text

 

}

 

from

 

'@platform-blocks/ui'

;

 

export

 

default

 

function

 

Demo

(

)

 

{

  

return

 

(

    

<Surface

 

level

=

{

0

}

 

padding

=

"md"

 

radius

=

"lg"

fullWidth

>

      

<Block

>

        

<Text

 

size

=

"sm"

 

colorVariant

=

"muted"

>

          

Level

 

0

— the page

        

</Text

>

        

<Surface

raised

padding

=

"md"

 

radius

=

"lg"

fullWidth

>

          

<Block

>

            

<Text

 

size

=

"sm"

>

Raised

once → level

1

</Text

>

            

<Surface

raised

padding

=

"md"

 

radius

=

"md"

fullWidth

>

              

<Text

 

size

=

"sm"

>

Raised

again → level

2

</Text

>

            

</Surface

>

          

</Block

>

        

</Surface

>

      

</Block

>

    

</Surface

>

  

)

;

}

Surface vs Card

Card is a Surface with padding and section semantics on top. Reach for Surface directly when you want the elevation without Card's structure — a toolbar, a sheet, a custom panel.

Loading demo…

import

 

{

 

Block

,

 

Card

,

 

Row

,

 

Surface

,

 

Text

 

}

 

from

 

'@platform-blocks/ui'

;

 

export

 

default

 

function

 

Demo

(

)

 

{

  

return

 

(

    

<Block

fullWidth

>

      

<Surface

 

level

=

{

1

}

 

padding

=

"md"

 

radius

=

"lg"

fullWidth

>

        

<Row

 

align

=

"center"

 

justify

=

"space-between"

>

          

<Text

 

size

=

"sm"

>

Surface

— elevation only

</Text

>

          

<Text

 

size

=

"sm"

 

colorVariant

=

"muted"

>

            level

1

          

</Text

>

        

</Row

>

      

</Surface

>

 

      

<Card

 

variant

=

"elevated"

 

padding

=

"md"

 

radius

=

"lg"

fullWidth

>

        

<Row

 

align

=

"center"

 

justify

=

"space-between"

>

          

<Text

 

size

=

"sm"

>

Card

Surface

 

+

padding

+

sections

</Text

>

          

<Text

 

size

=

"sm"

 

colorVariant

=

"muted"

>

            level

2

          

</Text

>

        

</Row

>

      

</Card

>

    

</Block

>

  

)

;

}

Platform Blocks

A comprehensive React Native design system with components that work seamlessly across iOS, Android, and Web.