The React Magma component library has been intentionally designed for use across multiple platforms. It is recommended to use the default styles that come with any component whenever possible. However, there may be occasions where overriding the default styles is required, and this can be accomplished in a number of ways.
Style Prop
Custom styles can be passed to components using the style
prop.
Magma variables can also be used in the style prop to apply themed styles on the fly.
import React from 'react';import { Button, Paragraph, magma } from 'react-magma-dom';export function Example() {return (<><Button style={{ borderRadius: '40px', minWidth: '300px' }}>Custom Button</Button><Paragraph>A paragraph with some inline{' '}<span style={{ color: magma.colors.success, fontWeight: 'bold' }}>success</span>{' '}styles.</Paragraph></>);}
Style Props for Complex Components
Some non-atomic components support styling of individual elements within the component via use of specific props.
For example, the Checkbox supports the following properties: containerStyle
, inputStyle
and labelStyle
. In these cases, the style
prop would have no effect because there is more than one element to target.
import React from 'react';import { Checkbox } from 'react-magma-dom';export function Example() {return (<CheckboxcheckedcontainerStyle={{ border: '1px dotted purple' }}inputStyle={{ boxShadow: '3px 3px 3px rgba(0, 0 , 0, .6)' }}labelStyle={{ color: 'brown', fontStyle: 'italic' }}labelText="Custom Styles"/>);}
Extending Styles With Emotion
React Magma styles are built with the Emotion library using styled-components.
If you are using Emotion in your app, you can extend the styles of React Magma components. This is useful if you want to add custom styles that do not fit into the style
prop, such as styles for pseudo elements, pseudo classes, and media queries.
Note: If the styles in your app are injected before the React Magma styles, your styles might not get applied if the specificity is the same. To ensure your styles are applied, you can use use the ampersand (&) character, as shown in this example.
import React from 'react';import styled from '@emotion/styled';import { Button } from 'react-magma-dom';const StyledButton = styled(Button)`&& {background: purple;&&:hover,&&:focus {background: green;}}`;export function Example() {return <StyledButton>Demo button</StyledButton>;}
Using Themes
By default, all components in the React Magma library use the Magma theme. You can use the ThemeContext to pass in a custom theme.
Use the <ThemeContext.Provider>
to pass in an alternative theme using the value
property. Any descendants of the context provider will receive the theme that is passed in.
Use the dropdown to change the theme for the demo component.
import React from 'react';import {Button,ButtonColor,Card,CardBody,CardHeading,Input,Radio,RadioGroup,Select,ThemeContext,magma,ButtonGroup,Spacer,} from 'react-magma-dom';const magmaDark = Object.assign({}, magma, {colors: Object.assign({}, magma.colors, {primary: magma.colors.secondary600, // link colorfocus: '#FFFFFF',danger: magma.colors.danger200,neutral: '#DFDFDF',neutral100: '#000000',neutral200: '#F7F7F7',neutral300: '#DFDFDF',neutral400: '#BFBFBF',neutral500: '#8f8f8f',neutral600: '#707070',neutral900: '#575757',neutral800: '#3F3F3F',neutral700: '#ffffff',border: magma.colors.secondary600,}),});function DemoComponent() {return (<Card><CardBody><CardHeading level={3}>Theme Example</CardHeading><Input labelText="Text input" helperMessage="Helper text" /><Input labelText="Text input with error" errorMessage="Error message" /><RadioGroup labelText="Radio Group" name="radio"><Radio labelText="Radio 1" value="1" /><Radio labelText="Radio 2" value="2" /></RadioGroup><ButtonGroup><Button>Button</Button><Button color={ButtonColor.secondary}>Secondary Button</Button><Button color={ButtonColor.danger}>Danger Button</Button></ButtonGroup></CardBody></Card>);}export function Example() {const [theme, setTheme] = React.useState(magma);function handleItemChange(changes) {setTheme(changes.selectedItem.value);}return (<div><Selectid="themeSwitcher"labelText="Select Theme"onSelectedItemChange={handleItemChange}items={[{value: magma,label: 'Magma (default)',},{value: magmaDark,label: 'Magma Dark',},]}/><Spacer size="24" /><ThemeContext.Provider value={theme}><DemoComponent /></ThemeContext.Provider></div>);}
Setting up a Theme
To implement theming in your project, we recommend a theme file that contains a theme object that matches React Magma's existing theme structure. The complete Magma theme can be found in the Github repository. You'll have to wrap your entire app - or individual components, for more specific theme changes - in the ThemeContext provider from React Magma. Any descendants of the context provider will receive the theme that is passed in. Individual theme elements can be accessed using context.
A project can have multiple theme files that are triggered conditionally by the Theme Provider.
The code below is an example of a theme file (applying to all components), including a themes for all valid individual components (only applies to those components - see subsection below for more information).
import { magma } from 'react-magma-dom';const typeScale = {size01: {fontSize: '12px',letterSpacing: '.32px',lineHeight: '16px',},size02: {fontSize: '14px',letterSpacing: '.16px',lineHeight: '20px',},size03: {fontSize: '16px',lineHeight: '24px',},size04: {fontSize: '18px',lineHeight: '24px',},size05: {fontSize: '20px',lineHeight: '32px',},size06: {fontSize: '24px',lineHeight: '32px',},size07: {fontSize: '28px',lineHeight: '40px',},size08: {fontSize: '32px',lineHeight: '40px',},size09: {fontSize: '36px',lineHeight: '48px',},size10: {fontSize: '42px',lineHeight: '48px',},size11: {fontSize: '48px',lineHeight: '56px',},size12: {fontSize: '54px',lineHeight: '64px',},size13: {fontSize: '60px',lineHeight: '72px',},};const primaryColors = {primary100: '#E2F4FE',primary200: '#B1E3FE',primary300: '#4FC0FF',primary400: '#1EAFFF',primary500: '#006298',primary600: '#003865',primary700: '#00263E',};const secondaryColors = {secondary500: '#FFC72C',secondary600: '#FFCE47',secondary700: '#F2A900',};const tertiaryColors = {tertiary500: '#CDDEFF',tertiary600: '#78A6FF',tertiary700: '#4785FF',};const neutralColors = {neutral100: '#FFFFFF', // whiteneutral200: '#F7F7F7',neutral300: '#DFDFDF',neutral400: '#BFBFBF',neutral500: '#707070',neutral600: '#575757',neutral700: '#3F3F3F',neutral800: '#2D2D2D',neutral900: '#000000', // blackneutral110: '#EDEDED',};const infoColors = {info100: '#E8F5FC',info200: '#70CDFF',info300: '#2FB3FF',info400: '#009AF3',info500: '#027EE1',info600: '#005F96',info700: '#004A75',};const dangerColors = {danger100: '#FDEFEE',danger200: '#F59295',danger300: '#E8716D',danger400: '#E24943',danger500: '#C61D23',danger600: '#A91F1A',danger700: '#7F1714',};const warningColors = {warning100: '#FCEEE5',warning200: '#F6CDB2',warning300: '#E98B4C',warning400: '#E06A1C',warning500: '#AD5115',warning600: '#8D4311',warning700: '#6E340E',};const successColors = {success100: '#E3FAEA',success200: '#91CF60',success300: '#3EDD6E',success400: '#21B94E',success500: '#3A8200',success600: '#136A2D',success700: '#0F5323',};const colors = {primary: primaryColors.primary500,secondary: secondaryColors.secondary500,tertiary: tertiaryColors.tertiary500,neutral: neutralColors.neutral500,info: infoColors.info500,danger: dangerColors.danger500,warning: warningColors.warning500,success: successColors.success500,...primaryColors,...secondaryColors,...tertiaryColors,...neutralColors,...infoColors,...dangerColors,...warningColors,...successColors,focus: infoColors.info500,focusInverse: infoColors.info200,border: neutralColors.neutral300,borderInverse: 'rgba(255,255,255,0.25)',};const spaceScale = {spacing01: '2px',spacing02: '4px',spacing03: '8px',spacing04: '12px',spacing05: '16px',spacing06: '24px',spacing07: '28px',spacing08: '32px',spacing09: '40px',spacing10: '48px',spacing11: '56px',spacing12: '64px',spacing13: '96px',spacing14: '160px',};
Setting Themes for Individual Components
To apply component-specific theming, theme objects can be included in the theme file for the following individual components:
- AppBar
- Combobox
- Modal
- Drawer
- Dropdown
- Tabs
- Select
- Tag
- Tooltip
These individual theme objects will be applied globally to only those components. The theme object for each above component can be found on its documentation page. See below for an example of an individual component's theme object:
tooltip: {arrowSize: '4px',arrowSizeDoubled: '8px',backgroundColor: colors.neutral,fontWeight: 600,maxWidth: '300px',textColor: colors.neutral100,typeScale: typeScale.size01,zIndex: 999,inverse: {backgroundColor: colors.neutral100,textColor: colors.neutral,}}
Typography Scale
One of the features of the Magma theme is a typography scale, that contains thirteen font-size/line-height combinations, to better ensure consistency. Designers are advised not to deviate from these combinations.
The typeScale
can be set as part of the theme.
typeScale: {size01: {fontSize: '12px',letterSpacing: '.32px',lineHeight: '16px',},size02: {fontSize: '14px',letterSpacing: '.16px',lineHeight: '20px',},size03: {fontSize: '16px',letterSpacing: '.32px',lineHeight: '24px',},size04: {fontSize: '18px',lineHeight: '24px',},size05: {fontSize: '20px',lineHeight: '32px',},size06: {fontSize: '24px',lineHeight: '32px',},size07: {fontSize: '28px',lineHeight: '40px',},size08: {fontSize: '32px',lineHeight: '40px',},size09: {fontSize: '36px',lineHeight: '48px',},size10: {fontSize: '42px',lineHeight: '48px',},size11: {fontSize: '48px',lineHeight: '56px',},size12: {fontSize: '52px',lineHeight: '68px',},size13: {fontSize: '54px',lineHeight: '64px',},size14: {fontSize: '60px',lineHeight: '72px',},size15: {fontSize: '72px',lineHeight: '84px',}}
Furthermore, font-size and line-height can be set in the theme for six heading levels and four paragraph relative sizes for two different screen resolutions. The theme also contains options for two additional typography variants for those heading and paragraph styles.
typographyVisualStyles: {headingXLarge: {mobile: typeScale.size07,desktop: typeScale.size09,fontWeight: 600,},headingLarge: {mobile: typeScale.size06,desktop: typeScale.size07,fontWeight: 600,},headingMedium: {mobile: typeScale.size05,desktop: typeScale.size06,fontWeight: 600,},...},typographyExpressiveVisualStyles: {heading2XLarge: {mobile: typeScale.size11,desktop: typeScale.size15,fontWeight: 500,},headingXLarge: {mobile: typeScale.size09,desktop: typeScale.size11,fontWeight: 600,},headingLarge: {mobile: typeScale.size07,desktop: typeScale.size09,fontWeight: 600,},headingMedium: {mobile: typeScale.size06,desktop: typeScale.size07,fontWeight: 600,},...},typographyNarrativeVisualStyles: {headingXLarge: {mobile: typeScale.size07,desktop: typeScale.size08,fontWeight: 700},headingLarge: {mobile: typeScale.size06,desktop: typeScale.size07,fontWeight: 700},headingMedium: {mobile: typeScale.size05,desktop: typeScale.size06,fontWeight: 700},...}
Space Scale
The Magma theme also has a spacing scale that can be used to implement padding or margins in a consistent fashion. Designers are advised to use values from the space scale whenever possible.
The spaceScale
can be overridden in by defining its values new theme.
spaceScale: {spacing01: '2px',spacing02: '4px',spacing03: '8px',spacing04: '12px',spacing05: '16px',spacing06: '24px',spacing07: '28px',spacing08: '32px',spacing09: '40px',spacing10: '48px',spacing11: '56px',spacing12: '64px',spacing13: '96px',spacing14: '160px'}
Customization
This isn't a recommended usage of the Magma components, however it showcases a potential for modification where applicable in varying scenarios.
import React from 'react';import {AppBar,Button,ButtonGroupAlignment,ButtonColor,ButtonGroup,ButtonVariant,Card,CardBody,Combobox,Flex,FlexBehavior,Heading,IconButton,magma,Radio,RadioGroup,Spacer,Table,TableHead,TableRow,TableHeaderCell,TableBody,TableCell,TabsContainer,Tabs,Tab,TabPanelsContainer,TabPanel,Tag,Tooltip,ThemeContext,} from 'react-magma-dom';import { InfoIcon } from 'react-magma-icons';import styled from '../../theme/styled';const springfieldDonut = Object.assign({}, magma, {colors: Object.assign({}, magma.colors, {danger: '#f3772b',danger600: '#b94d1c',marketing: '#1a85e0',neutral500: '#893013',primary: '#3493f1',primary600: '#215fb3',neutral: '#7b361e',neutral100: '#ebf88b',secondary500: '#42e093',secondary600: '#43fd94',}),bodyFont: '"Varela Round", sans-serif',bodyExpressiveFont: '"Varela Round", sans-serif',bodyNarrativeFont: '"Varela Round", sans-serif',borderRadius: '18px',borderRadiusSmall: '14px',headingFont: '"Varela Round", sans-serif',headingExpressiveFont: '"Varela Round", sans-serif',headingNarrativeFont: '"Varela Round", sans-serif',direction: 'ltr',spacingMultiplier: 8,// breakpointsbreakpoints: {xs: 60,small: 800,medium: 900,large: 2000,xl: 5000,},spaceScale: magma.spaceScale,iconSizes: {xSmall: 10,small: 14,medium: 18,large: 22,xLarge: 26,},// TypographytypeScale: magma.typeScale,typographyVisualStyles: {// ProductiveheadingXLarge: {mobile: magma.typeScale.size07,desktop: magma.typeScale.size09,fontWeight: 900,},headingLarge: {mobile: magma.typeScale.size06,desktop: magma.typeScale.size07,fontWeight: 900,},headingMedium: {mobile: magma.typeScale.size05,desktop: magma.typeScale.size06,fontWeight: 900,},headingSmall: {mobile: magma.typeScale.size04,desktop: magma.typeScale.size05,fontWeight: 900,},headingXSmall: {mobile: magma.typeScale.size04,desktop: magma.typeScale.size04,fontWeight: 900,},heading2XSmall: {mobile: magma.typeScale.size01,desktop: magma.typeScale.size01,fontWeight: 900,},bodyLarge: {mobile: magma.typeScale.size04,desktop: magma.typeScale.size05,},bodyMedium: {mobile: magma.typeScale.size06,desktop: magma.typeScale.size06,},bodySmall: {mobile: magma.typeScale.size02,desktop: magma.typeScale.size02,},bodyXSmall: {mobile: magma.typeScale.size01,desktop: magma.typeScale.size01,},},typographyExpressiveVisualStyles: {heading2XLarge: {mobile: magma.typeScale.size13,desktop: magma.typeScale.size15,fontWeight: 900,},headingXLarge: {mobile: magma.typeScale.size10,desktop: magma.typeScale.size12,fontWeight: 900,},headingLarge: {mobile: magma.typeScale.size07,desktop: magma.typeScale.size09,fontWeight: 900,},headingMedium: {mobile: magma.typeScale.size06,desktop: magma.typeScale.size07,fontWeight: 900,},headingSmall: {mobile: magma.typeScale.size05,desktop: magma.typeScale.size06,fontWeight: 900,},headingXSmall: {mobile: magma.typeScale.size04,desktop: magma.typeScale.size05,fontWeight: 900,},heading2XSmall: {mobile: magma.typeScale.size03,desktop: magma.typeScale.size03,fontWeight: 900,},bodyLarge: {mobile: magma.typeScale.size05,desktop: magma.typeScale.size06,},bodyMedium: {mobile: magma.typeScale.size03,desktop: magma.typeScale.size03,},bodySmall: {mobile: magma.typeScale.size02,desktop: magma.typeScale.size02,},bodyXSmall: {mobile: magma.typeScale.size01,desktop: magma.typeScale.size01,},},typographyNarrativeVisualStyles: {headingXLarge: {mobile: magma.typeScale.size07,desktop: magma.typeScale.size08,fontWeight: 900,},headingLarge: {mobile: magma.typeScale.size06,desktop: magma.typeScale.size07,fontWeight: 900,},headingMedium: {mobile: magma.typeScale.size05,desktop: magma.typeScale.size06,fontWeight: 900,},headingSmall: {mobile: magma.typeScale.size04,desktop: magma.typeScale.size05,fontWeight: 900,},headingXSmall: {mobile: magma.typeScale.size04,desktop: magma.typeScale.size04,fontWeight: 900,},heading2XSmall: {mobile: magma.typeScale.size01,desktop: magma.typeScale.size01,fontWeight: 900,},bodyLarge: {mobile: magma.typeScale.size04,desktop: magma.typeScale.size05,},bodyMedium: {mobile: magma.typeScale.size03,desktop: magma.typeScale.size03,},bodySmall: {mobile: magma.typeScale.size02,desktop: magma.typeScale.size02,},bodyXSmall: {mobile: magma.typeScale.size01,desktop: magma.typeScale.size01,},},appBar: {backgroundColor: '#d792cb',textColor: '#ebf88b',height: '80px',padding: '0 30px',compact: {height: '56px',padding: `${magma.spaceScale.spacing05} ${magma.spaceScale.spacing05} ${magma.spaceScale.spacing05} ${magma.spaceScale.spacing06}`,},inverse: {backgroundColor: magma.colors.primary600,textColor: magma.colors.neutral100,},},combobox: {menu: {maxHeight: '250px',},},modal: {width: {small: '300px',medium: '600px',large: '900px',},},drawer: {default: {maxWidth: '50%',minHeight: '500px',margin: '0',borderRadius: '0',top: '0',right: 'calc(50% - 25%)',bottom: '0',left: 'calc(50% - 25%)',position: 'relative',},right: {left: 'calc(50% - 25%)',height: '50%',width: '500px',position: 'fixed',},left: {right: 'auto',height: '50%',width: '500px',},top: {bottom: 'auto',height: '500px',},bottom: {top: 'auto',height: '500px',position: 'fixed',},},dropdown: {content: {maxHeight: '250px',},},tabs: {approxTabSize: {horizontal: 120,vertical: 80,},},select: {menu: {maxHeight: '420px',},},tag: {border: '0',display: 'inline-flex',alignItems: 'center',justifyContent: 'space-around',},tooltip: {arrowSize: '4px',arrowSizeDoubled: '8px',backgroundColor: '#15397c',fontWeight: 900,maxWidth: '300px',textColor: '#f7db7f',typeScale: magma.typeScale.size02,zIndex: 999,inverse: {backgroundColor: magma.colors.neutral,textColor: '#f7db7f',},},});const StyledCardHeading = styled.h1`color: #f7db7f;font-size: 40px;position: relative;-webkit-text-stroke-width: 4px;text-shadow:-5px 1px #a15936,-5px -1px #a15936,-5px 0px 2px #a15936,-5px 4px 2px #a15936,1px 4px 2px #a15936;&:before {color: white;content: 'Doh';position: absolute;top: 0;left: 5px;font-size: 0.7em;letter-spacing: 8px;filter: blur(3px);opacity: 0.3;}`;export function Example() {//Shows / hides the appropriate step on click.const [eachIndex, setEachIndex] = React.useState(0);function handleChange(eachIndex) {setEachIndex(eachIndex);}return (<ThemeContext.Provider value={springfieldDonut}><style>@import url({`https://fonts.googleapis.com/css2?family=Varela+Round&display=swap`});</style><Card isInverse><CardBody><AppBar style={{ borderRadius: '6px' }}><StyledCardHeading>Doh</StyledCardHeading></AppBar><Spacer size={20} /><TabsContainer isInverse activeIndex={eachIndex}><Tabs isInverse onChange={handleChange}><Tab>Donuts</Tab>{eachIndex === 0 ? (<Tab disabled>Shipping</Tab>) : (<Tab>Shipping</Tab>)}{eachIndex === 0 || eachIndex === 1 ? (<Tab disabled>Summary</Tab>) : (<Tab>Summary</Tab>)}</Tabs><Spacer size={20} /><TabPanelsContainer><TabPanel><HeadingisInversecontextVariant={TypographyContextVariant.expressive}level={4}>Donut selecta</Heading><ComboboxisInverseid="comboboxId"labelText="Variety"defaultItems={[{ label: 'Glazed', value: 'glazed' },{ label: 'Chocolate', value: 'chocolate' },{ label: 'Sprinkles', value: 'sprinkles' },{ label: 'Remix!', value: 'remix' },]}placeholder="Choose.."/><Spacer size={40} /><Flex behavior={FlexBehavior.container}><Flex behavior={FlexBehavior.item} md={5}><Card isInverse><CardBody><RadioGroupisInverselabelText="Donuts per box"name="donuts"><Tooltip content="How many of these are you planning to indulge?"><IconButtonaria-label="Right"icon={<InfoIcon />}isInversestyle={{ marginLeft: magma.spaceScale.spacing03 }}variant={ButtonVariant.link}/></Tooltip><Radio labelText="5" value="5" /><Radio labelText="10" value="10" /><Radio labelText="20" value="20" /><Radio labelText="40" value="40" /></RadioGroup></CardBody></Card></Flex><Spacer size={20} /><Flex behavior={FlexBehavior.item} md={5}><Card isInverse><CardBody><RadioGroup isInverse labelText="Boxes" name="boxes"><Tooltip content="I hope you'll be distributing these along your street."><IconButtonaria-label="Right"icon={<InfoIcon />}isInversestyle={{ marginLeft: magma.spaceScale.spacing03 }}variant={ButtonVariant.link}/></Tooltip><Radio labelText="1" value="1" /><Radio labelText="2" value="2" /><Radio labelText="3" value="3" /><Radio labelText="4" value="4" /></RadioGroup></CardBody></Card></Flex></Flex><Spacer size={40} /><ButtonGroup alignment={ButtonGroupAlignment.right}><ButtononClick={() => setEachIndex(1)}color={ButtonColor.secondary}>Continue</Button></ButtonGroup></TabPanel><TabPanel><HeadingisInversecontextVariant={TypographyContextVariant.expressive}level={4}>How quickly in need are we?</Heading><RadioGroupisInverselabelText="Select your patience level"name="donuts"value="selectedOption"><Tooltip content="An expanse of time during which you'll potentially contend with a donut free lifestyle."><IconButtonaria-label="Right"icon={<InfoIcon />}isInversevariant={ButtonVariant.link}/></Tooltip><Card isInverse><CardBody><Flex behavior={FlexBehavior.container}><Flex behavior={FlexBehavior.item} md={2}><Tag color="primary" isInverse size="medium">Free!</Tag></Flex><Spacer size={20} /><Flex behavior={FlexBehavior.item} md={8}><RadiolabelText="A standard and deliberately slowed shipping duration due to your thriftiness."value="selectedOption"/></Flex></Flex></CardBody></Card><Spacer size={20} /><Card isInverse><CardBody><Flex behavior={FlexBehavior.container}><Flex behavior={FlexBehavior.item} md={2}><Tag color="primary" isInverse size="medium">$20</Tag></Flex><Spacer size={20} /><Flex behavior={FlexBehavior.item} md={8}><RadiolabelText="Upgraded and moderately expedited."value="middling"/></Flex></Flex></CardBody></Card><Spacer size={20} /><Card isInverse><CardBody><Flex behavior={FlexBehavior.container}><Flex behavior={FlexBehavior.item} md={2}><Tag color="primary" isInverse size="medium">$30</Tag></Flex><Spacer size={20} /><Flex behavior={FlexBehavior.item} md={8}><RadiolabelText="Teleportation-like response time."value="fancy"/></Flex></Flex></CardBody></Card></RadioGroup><Spacer size={40} /><ButtonGroup alignment={ButtonGroupAlignment.right}><ButtononClick={() => setEachIndex(0)}color={ButtonColor.primary}>Back</Button><ButtononClick={() => setEachIndex(2)}color={ButtonColor.secondary}>Continue</Button></ButtonGroup></TabPanel><TabPanel><HeadingisInversecontextVariant={TypographyContextVariant.expressive}level={4}>Order summary</Heading><Table isInverse hasVerticalBorders><TableHead><TableRow><TableHeaderCell>Variety</TableHeaderCell><TableHeaderCell>Donuts per box</TableHeaderCell><TableHeaderCell>Boxes</TableHeaderCell></TableRow></TableHead><TableBody><TableRow><TableCell>Remix!</TableCell><TableCell>40</TableCell><TableCell>3</TableCell></TableRow></TableBody></Table><Spacer size={40} /><ButtonGroup alignment={ButtonGroupAlignment.right}><ButtononClick={() => setEachIndex(1)}color={ButtonColor.primary}>Back</Button><ButtononClick={() => setEachIndex(0)}color={ButtonColor.secondary}>Place Order</Button></ButtonGroup></TabPanel></TabPanelsContainer></TabsContainer></CardBody></Card></ThemeContext.Provider>);}
On this page