Repository: polkadot-js/extension
Pull request #518 (2020-10-21 15:30:15)

Style component global cleanup

- Not touching https://github.com/polkadot-js/extension/blob/bc4e57e353940dbabb8a53ae7dc63e7038b924ea/packages/extension-ui/src/components/TextInputs.ts since I would need to duplicate the styling, I don't think it would make sense. 

Tbaut: Other than the couple comments I left, there should be nothing new/changed.
Tbaut: Thanks, I've been trying to test every step very carefully. Hope I didn't miss anything important.
I'll resolve conflicts and merge.

Patch:
diff --git a/packages/extension-ui/src/Popup/Accounts/index.tsx b/packages/extension-ui/src/Popup/Accounts/index.tsx
index 7cd16a64..749a0172 100644
--- a/packages/extension-ui/src/Popup/Accounts/index.tsx
+++ b/packages/extension-ui/src/Popup/Accounts/index.tsx
@@ -1,6 +1,8 @@
 // Copyright 2019-2020 @polkadot/extension-ui authors & contributors
 // SPDX-License-Identifier: Apache-2.0
 
+import { ThemeProps } from '../../types';
+
 import React, { useContext } from 'react';
 import styled from 'styled-components';
 
@@ -10,7 +12,11 @@ import { Header } from '../../partials';
 import AccountsTree from './AccountsTree';
 import AddAccount from './AddAccount';
 
-export default function Accounts (): React.ReactElement {
+interface Props extends ThemeProps {
+  className?: string;
+}
+
+function Accounts ({ className }: Props): React.ReactElement {
   const { t } = useTranslation();
   const { hierarchy } = useContext(AccountContext);
 
@@ -25,14 +31,14 @@ export default function Accounts (): React.ReactElement {
               showSettings
               text={t<string>('Accounts')}
             />
-            <AccountsArea>
+            <div className={className}>
               {hierarchy.map((json, index): React.ReactNode => (
                 <AccountsTree
                   {...json}
                   key={`${index}:${json.address}`}
                 />
               ))}
-            </AccountsArea>
+            </div>
           </>
         )
       }
@@ -40,7 +46,7 @@ export default function Accounts (): React.ReactElement {
   );
 }
 
-const AccountsArea = styled.div`
+export default styled(Accounts)`
   height: 100%;
   overflow-y: scroll;
   margin-top: -25px;
diff --git a/packages/extension-ui/src/Popup/Authorize/Request.tsx b/packages/extension-ui/src/Popup/Authorize/Request.tsx
index 20ab44a8..3602e49b 100644
--- a/packages/extension-ui/src/Popup/Authorize/Request.tsx
+++ b/packages/extension-ui/src/Popup/Authorize/Request.tsx
@@ -12,7 +12,7 @@ import { ActionBar, ActionContext, Button, Icon, Link, Warning } from '../../com
 import useTranslation from '../../hooks/useTranslation';
 import { approveAuthRequest, rejectAuthRequest } from '../../messaging';
 
-interface Props {
+interface Props extends ThemeProps {
   authId: string;
   className?: string;
   isFirst: boolean;
@@ -40,8 +40,8 @@ function Request ({ authId, className, isFirst, request: { origin }, url }: Prop
 
   return (
     <div className={className}>
-      <RequestInfo>
-        <Info>
+      <div className='requestInfo'>
+        <div className='info'>
           <Icon
             icon='X'
             onClick={_onReject}
@@ -57,59 +57,35 @@ function Request ({ authId, className, isFirst, request: { origin }, url }: Prop
               </a>.
             </Trans>
           </div>
-        </Info>
+        </div>
         {isFirst && (
           <>
-            <RequestWarning>{t<string>('Only approve this request if you trust the application. Approving gives the application access to the addresses of your accounts.')}</RequestWarning>
-            <AcceptButton onClick={_onApprove}>{t<string>('Yes, allow this application access')}</AcceptButton>
+            <Warning className='warningMargin'>{t<string>('Only approve this request if you trust the application. Approving gives the application access to the addresses of your accounts.')}</Warning>
+            <Button
+              className='acceptButton'
+              onClick={_onApprove}
+            >
+              {t<string>('Yes, allow this application access')}
+            </Button>
           </>
         )}
-        <RejectButton>
+        <ActionBar className='rejectionButton'>
           <Link
             isDanger
             onClick={_onReject}
           >
             Reject
           </Link>
-        </RejectButton>
-      </RequestInfo>
+        </ActionBar>
+      </div>
     </div>
   );
 }
 
-const RequestInfo = styled.div`
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  margin-bottom: 8px;
-  background: ${({ theme }: ThemeProps): string => theme.highlightedAreaBackground};
-`;
-
-const Info = styled.div`
-  display: flex;
-  flex-direction: row;
-`;
-
-const AcceptButton = styled(Button)`
-  width: 90%;
-  margin: 25px auto 0;
-`;
-
-const RequestWarning = styled(Warning)`
-  margin: 24px 24px 0 1.45rem;
-`;
-
-AcceptButton.displayName = 'AcceptButton';
-
-const RejectButton = styled(ActionBar)`
-  margin: 8px 0 15px 0;
-  text-decoration: underline;
-`;
-
-export default styled(Request)`
+export default styled(Request)(({ theme }: Props) => `
 
   .icon {
-    background: ${({ theme }: ThemeProps): string => theme.buttonBackgroundDanger};
+    background: ${theme.buttonBackgroundDanger};
     color: white;
     min-width: 18px;
     width: 14px;
@@ -128,7 +104,7 @@ export default styled(Request)`
 
   .tab-name,
   .tab-url {
-    color: ${({ theme }: ThemeProps): string => theme.textColor};
+    color: ${theme.textColor};
     display: inline-block;
     max-width: 20rem;
     overflow: hidden;
@@ -137,4 +113,31 @@ export default styled(Request)`
     cursor: pointer;
     text-decoration: underline;
   }
-`;
+
+  .requestInfo {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    margin-bottom: 8px;
+    background: ${theme.highlightedAreaBackground};
+  }
+
+  .info {
+    display: flex;
+    flex-direction: row;
+  }
+
+  .acceptButton {
+    width: 90%;
+    margin: 25px auto 0;
+  }
+
+  .warningMargin {
+    margin: 24px 24px 0 1.45rem;
+  }
+
+  .rejectionButton {
+    margin: 8px 0 15px 0;
+    text-decoration: underline;
+  }
+`);
diff --git a/packages/extension-ui/src/Popup/Authorize/index.tsx b/packages/extension-ui/src/Popup/Authorize/index.tsx
index d8219142..abf43cfe 100644
--- a/packages/extension-ui/src/Popup/Authorize/index.tsx
+++ b/packages/extension-ui/src/Popup/Authorize/index.tsx
@@ -1,6 +1,8 @@
 // Copyright 2019-2020 @polkadot/extension-ui authors & contributors
 // SPDX-License-Identifier: Apache-2.0
 
+import { ThemeProps } from '../../types';
+
 import React, { useContext } from 'react';
 import styled from 'styled-components';
 
@@ -9,36 +11,45 @@ import useTranslation from '../../hooks/useTranslation';
 import { Header } from '../../partials';
 import Request from './Request';
 
-export default function Authorize (): React.ReactElement {
+interface Props extends ThemeProps {
+  className?: string;
+}
+
+function Authorize ({ className = '' }: Props): React.ReactElement {
   const { t } = useTranslation();
   const requests = useContext(AuthorizeReqContext);
 
   return (
     <>
-      <Scroll isLastRequest={requests.length === 1}>
+      <div className={`${className} ${requests.length === 1 ? 'lastRequest' : ''}`}>
         <Header text={t<string>('Authorize')} />
         {requests.map(({ id, request, url }, index): React.ReactNode => (
           <Request
             authId={id}
+            className='request'
             isFirst={index === 0}
             key={id}
             request={request}
             url={url}
           />
         ))}
-      </Scroll>
+      </div>
     </>
   );
 }
 
-const Scroll = styled.div<{isLastRequest: boolean}>`
-  overflow-y: ${({ isLastRequest }): string => isLastRequest ? 'hidden' : 'auto'};
+export default styled(Authorize)`
+  overflow-y: auto;
+
+  &.lastRequest {
+    overflow: hidden;
+  }
 
   && {
     padding: 0;
   }
 
-  ${Request} {
+  .request {
     padding: 0 24px;
   }
 `;
diff --git a/packages/extension-ui/src/Popup/Derive/AddressDropdown.tsx b/packages/extension-ui/src/Popup/Derive/AddressDropdown.tsx
index 91ddabd7..5399886a 100644
--- a/packages/extension-ui/src/Popup/Derive/AddressDropdown.tsx
+++ b/packages/extension-ui/src/Popup/Derive/AddressDropdown.tsx
@@ -7,7 +7,7 @@ import React, { useCallback, useRef, useState } from 'react';
 import styled from 'styled-components';
 
 import arrow from '../../assets/arrow-down.svg';
-import { Address, Svg } from '../../components';
+import { Address } from '../../components';
 import useOutsideClick from '../../hooks/useOutsideClick';
 
 interface Props {
@@ -36,10 +36,11 @@ function AddressDropdown ({ allAddresses, className, onSelect, selectedAddress,
       >
         <Address
           address={selectedAddress}
+          className='address'
           genesisHash={selectedGenesis}
         />
       </div>
-      <HiddenOptions visible={isDropdownVisible}>
+      <div className={`dropdown ${isDropdownVisible ? 'visible' : ''}`}>
         {allAddresses.map(([address, genesisHash]) => (
           <div
             data-parent-option
@@ -48,39 +49,21 @@ function AddressDropdown ({ allAddresses, className, onSelect, selectedAddress,
           >
             <Address
               address={address}
+              className='address'
               genesisHash={genesisHash}
             />
           </div>
         ))}
-      </HiddenOptions>
+      </div>
     </div>
   );
 }
 
-const HiddenOptions = styled.div<{ visible: boolean }>`
-  position: absolute;
-  visibility: ${({ visible }): string => (visible ? 'visible' : 'hidden')};
-  width: 510px;
-  z-index: 100;
-  background: ${({ theme }: ThemeProps): string => theme.bodyColor};
-  max-height: ${({ visible }): string => (visible ? '200px' : '0')};
-  overflow: scroll;
-  padding: 5px;
-  border: 1px solid ${({ theme }: ThemeProps): string => theme.boxBorderColor};
-  box-sizing: border-box;
-  border-radius: 4px;
-  margin-top: -8px;
-
-  & > div {
-    cursor: pointer;
-  }
-`;
-
 export default styled(AddressDropdown)(({ theme }: ThemeProps) => `
   margin-bottom: 16px;
   cursor: pointer;
 
-  & > div:first-child > ${Address}::after {
+  & > div:first-child > .address::after {
     content: '';
     position: absolute;
     top: 66%;
@@ -95,7 +78,31 @@ export default styled(AddressDropdown)(({ theme }: ThemeProps) => `
     border: 1px solid ${theme.boxBorderColor};
   }
 
-  ${Address} ${Svg} {
+  .address .copyIcon {
+    visibility: hidden;
+  }
+
+  .dropdown {
+    position: absolute;
     visibility: hidden;
+    width: 510px;
+    z-index: 100;
+    background: ${theme.bodyColor};
+    max-height: 0;
+    overflow: auto;
+    padding: 5px;
+    border: 1px solid ${theme.boxBorderColor};
+    box-sizing: border-box;
+    border-radius: 4px;
+    margin-top: -8px;
+
+    &.visible{
+      visibility: visible;
+      max-height: 200px;
+    }
+
+    & > div {
+      cursor: pointer;
+    }
   }
 `);
diff --git a/packages/extension-ui/src/Popup/Derive/SelectParent.tsx b/packages/extension-ui/src/Popup/Derive/SelectParent.tsx
index 2a6b14b0..c4d5c841 100644
--- a/packages/extension-ui/src/Popup/Derive/SelectParent.tsx
+++ b/packages/extension-ui/src/Popup/Derive/SelectParent.tsx
@@ -1,6 +1,8 @@
 // Copyright 2019-2020 @polkadot/extension-ui authors & contributors
 // SPDX-License-Identifier: Apache-2.0
 
+import { ThemeProps } from '../../types';
+
 import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
 import styled from 'styled-components';
 import { assert } from '@polkadot/util';
@@ -12,14 +14,15 @@ import { nextDerivationPath } from '../../util/nextDerivationPath';
 import AddressDropdown from './AddressDropdown';
 import DerivationPath from './DerivationPath';
 
-interface Props {
+interface Props extends ThemeProps {
+  className?: string;
   isLocked?: boolean;
   parentAddress: string;
   parentGenesis: string | null;
   onDerivationConfirmed: (derivation: { account: { address: string; suri: string }; parentPassword: string }) => void;
 }
 
-export function SelectParent ({ isLocked, onDerivationConfirmed, parentAddress, parentGenesis }: Props): React.ReactElement<Props> {
+function SelectParent ({ className, isLocked, onDerivationConfirmed, parentAddress, parentGenesis }: Props): React.ReactElement<Props> {
   const { t } = useTranslation();
   const onAction = useContext(ActionContext);
   const [isBusy, setIsBusy] = useState(false);
@@ -84,58 +87,60 @@ export function SelectParent ({ isLocked, onDerivationConfirmed, parentAddress,
     setParentPassword('');
     setIsProperParentPassword(false);
 
-    // eslint-disable-next-line no-unused-expressions
     passwordInputRef.current?.querySelector('input')?.focus();
   }, [_onParentPasswordEnter]);
 
   return (
     <>
-      {!isLocked && (
-        <CheckboxWithSmallerMargins
-          checked={shouldAccountBeDerived}
-          label={t<string>('Derive new account from existing')}
-          onChange={setShouldAccountBeDerived}
-        />
-      )}
-      <DisableableArea isDisabled={!shouldAccountBeDerived}>
-        {isLocked
-          ? (
-            <Address
-              address={parentAddress}
-              genesisHash={parentGenesis}
-            />
-          )
-          : (
-            <Label label={t<string>('Choose Parent Account:')}>
-              <AddressDropdown
-                allAddresses={allAddresses}
-                onSelect={_onParentChange}
-                selectedAddress={parentAddress}
-                selectedGenesis={parentGenesis}
-              />
-            </Label>
-          )
-        }
-        <div ref={passwordInputRef}>
-          <InputWithLabel
-            data-export-password
-            isError={!isProperParentPassword}
-            isFocused
-            label={t<string>('enter the password for the account you want to derive from')}
-            onChange={_onParentPasswordEnter}
-            type='password'
-            value={parentPassword}
-          />
-        </div>
-        {isProperParentPassword && (
-          <DerivationPath
-            defaultPath={defaultPath}
-            onChange={setSuriPath}
-            parentAddress={parentAddress}
-            parentPassword={parentPassword}
+      <div className={className}>
+        {!isLocked && (
+          <Checkbox
+            checked={shouldAccountBeDerived}
+            className='smallerMargin'
+            label={t<string>('Derive new account from existing')}
+            onChange={setShouldAccountBeDerived}
           />
         )}
-      </DisableableArea>
+        <div className={`disableArea ${shouldAccountBeDerived ? '' : 'disabled'}`}>
+          {isLocked
+            ? (
+              <Address
+                address={parentAddress}
+                genesisHash={parentGenesis}
+              />
+            )
+            : (
+              <Label label={t<string>('Choose Parent Account:')}>
+                <AddressDropdown
+                  allAddresses={allAddresses}
+                  onSelect={_onParentChange}
+                  selectedAddress={parentAddress}
+                  selectedGenesis={parentGenesis}
+                />
+              </Label>
+            )
+          }
+          <div ref={passwordInputRef}>
+            <InputWithLabel
+              data-export-password
+              isError={!isProperParentPassword}
+              isFocused
+              label={t<string>('enter the password for the account you want to derive from')}
+              onChange={_onParentPasswordEnter}
+              type='password'
+              value={parentPassword}
+            />
+          </div>
+          {isProperParentPassword && (
+            <DerivationPath
+              defaultPath={defaultPath}
+              onChange={setSuriPath}
+              parentAddress={parentAddress}
+              parentPassword={parentPassword}
+            />
+          )}
+        </div>
+      </div>
       <VerticalSpace/>
       <ButtonArea>
         {shouldAccountBeDerived
@@ -163,12 +168,19 @@ export function SelectParent ({ isLocked, onDerivationConfirmed, parentAddress,
   );
 }
 
-const CheckboxWithSmallerMargins = styled(Checkbox)`
-  margin-top: 0;
-  margin-bottom: 10px;
-`;
+export default styled(SelectParent)`
+
+  .smallerMargin {
+    margin-top: 0;
+    margin-bottom: 10px;
+  }
+
+  .disableArea {
+    opacity: 1;
 
-const DisableableArea = styled.div<{ isDisabled: boolean }>`
-  opacity: ${({ isDisabled }): string => isDisabled ? '0.2' : '1'};
-  ${({ isDisabled }): string | false => isDisabled && 'pointer-events: none'};
+    .disabled {
+      opacity: 0.2;
+      pointer-events: none;
+    }
+  }
 `;
diff --git a/packages/extension-ui/src/Popup/Derive/index.tsx b/packages/extension-ui/src/Popup/Derive/index.tsx
index 70237643..8f3061c9 100644
--- a/packages/extension-ui/src/Popup/Derive/index.tsx
+++ b/packages/extension-ui/src/Popup/Derive/index.tsx
@@ -8,7 +8,7 @@ import { AccountContext, ActionContext, Address, BackButton, ButtonArea, NextSte
 import useTranslation from '../../hooks/useTranslation';
 import { deriveAccount } from '../../messaging';
 import { HeaderWithSteps, Name, Password } from '../../partials';
-import { SelectParent } from './SelectParent';
+import SelectParent from './SelectParent';
 
 interface Props {
   isLocked?: boolean;
diff --git a/packages/extension-ui/src/Popup/Export.tsx b/packages/extension-ui/src/Popup/Export.tsx
index fe7dc2b0..20bae1fa 100644
--- a/packages/extension-ui/src/Popup/Export.tsx
+++ b/packages/extension-ui/src/Popup/Export.tsx
@@ -1,6 +1,8 @@
 // Copyright 2019-2020 @polkadot/extension-ui authors & contributors
 // SPDX-License-Identifier: Apache-2.0
 
+import { ThemeProps } from '../types';
+
 import React, { useCallback, useContext, useState } from 'react';
 import { RouteComponentProps, withRouter } from 'react-router';
 
@@ -12,9 +14,11 @@ import styled from 'styled-components';
 
 const MIN_LENGTH = 6;
 
-type Props = RouteComponentProps<{ address: string }>;
+interface Props extends RouteComponentProps<{address: string}>, ThemeProps {
+  className?: string;
+}
 
-function Export ({ match: { params: { address } } }: Props): React.ReactElement<Props> {
+function Export ({ className, match: { params: { address } } }: Props): React.ReactElement<Props> {
   const { t } = useTranslation();
   const onAction = useContext(ActionContext);
   const [isBusy, setIsBusy] = useState(false);
@@ -58,10 +62,15 @@ function Export ({ match: { params: { address } } }: Props): React.ReactElement<
         showBackArrow
         text={t<string>('Export account')}
       />
-      <div>
+      <div className={className}>
         <Address address={address}>
-          <MovedWarning danger>{t<string>("You are exporting your account. Keep it safe and don't share it with anyone.")}</MovedWarning>
-          <ActionArea>
+          <Warning
+            className='movedWarning'
+            isDanger
+          >
+            {t<string>("You are exporting your account. Keep it safe and don't share it with anyone.")}
+          </Warning>
+          <div className='actionArea'>
             <InputWithLabel
               data-export-password
               disabled={isBusy}
@@ -80,34 +89,34 @@ function Export ({ match: { params: { address } } }: Props): React.ReactElement<
             >
               {t<string>('I want to export this account')}
             </Button>
-            <CancelButton>
+            <ActionBar className='withMarginTop'>
               <ActionText
+                className='center'
                 onClick={_goHome}
                 text={t<string>('Cancel')}
               />
-            </CancelButton>
-          </ActionArea>
+            </ActionBar>
+          </div>
         </Address>
       </div>
     </>
   );
 }
 
-const MovedWarning = styled(Warning)`
-  margin-top: 8px;
-`;
-
-const ActionArea = styled.div`
-  padding: 10px 24px;
-`;
-
-const CancelButton = styled(ActionBar)`
-  margin-top: 4px;
-  text-decoration: underline;
+export default withRouter(styled(Export)`
+  .actionArea {
+    padding: 10px 24px;
+  }
 
-  ${ActionText} {
+  .center {
     margin: auto;
   }
-`;
 
-export default withRouter(Export);
+  .movedWarning {
+    margin-top: 8px;
+  }
+
+  .withMarginTop {
+    margin-top: 4px;
+  }
+`);
diff --git a/packages/extension-ui/src/Popup/Forget.tsx b/packages/extension-ui/src/Popup/Forget.tsx
index b7c799b0..87f1d9ad 100644
--- a/packages/extension-ui/src/Popup/Forget.tsx
+++ b/packages/extension-ui/src/Popup/Forget.tsx
@@ -1,5 +1,6 @@
 // Copyright 2019-2020 @polkadot/extension-ui authors & contributors
 // SPDX-License-Identifier: Apache-2.0
+import { ThemeProps } from '../types';
 
 import React, { useCallback, useContext } from 'react';
 import { RouteComponentProps, withRouter } from 'react-router';
@@ -10,9 +11,11 @@ import useTranslation from '../hooks/useTranslation';
 import { forgetAccount } from '../messaging';
 import { Header } from '../partials';
 
-type Props = RouteComponentProps<{ address: string }>;
+interface Props extends RouteComponentProps<{ address: string }>, ThemeProps {
+  className?: string;
+}
 
-function Forget ({ match: { params: { address } } }: Props): React.ReactElement<Props> {
+function Forget ({ className, match: { params: { address } } }: Props): React.ReactElement<Props> {
   const { t } = useTranslation();
   const onAction = useContext(ActionContext);
 
@@ -35,43 +38,49 @@ function Forget ({ match: { params: { address } } }: Props): React.ReactElement<
         showBackArrow
         text={t<string>('Forget account')}
       />
-      <div>
+      <div className={className}>
         <Address address={address}>
-          <MovedWarning isDanger>{t<string>('You are about to remove the account. This means that you will not be able to access it via this extension anymore. If you wish to recover it, you would need to use the seed.')}</MovedWarning>
-          <ActionArea>
+          <Warning
+            className='movedWarning'
+            isDanger
+          >
+            {t<string>('You are about to remove the account. This means that you will not be able to access it via this extension anymore. If you wish to recover it, you would need to use the seed.')}
+          </Warning>
+          <div className='actionArea'>
             <Button
               isDanger
               onClick={_onClick}
             >
               {t<string>('I want to forget this account')}
             </Button>
-            <CancelButton>
+            <ActionBar className='withMarginTop'>
               <ActionText
+                className='center'
                 onClick={_goHome}
                 text={t<string>('Cancel')}
               />
-            </CancelButton>
-          </ActionArea>
+            </ActionBar>
+          </div>
         </Address>
       </div>
     </>
   );
 }
 
-const MovedWarning = styled(Warning)`
-  margin-top: 8px;
-`;
-
-const ActionArea = styled.div`
-  padding: 10px 24px;
-`;
-
-const CancelButton = styled(ActionBar)`
-  margin-top: 4px;
+export default withRouter(styled(Forget)`
+  .actionArea {
+    padding: 10px 24px;
+  }
 
-  ${ActionText} {
+  .center {
     margin: auto;
   }
-`;
 
-export default withRouter(Forget);
+  .movedWarning {
+    margin-top: 8px;
+  }
+
+  .withMarginTop {
+    margin-top: 4px;
+  }
+`);
diff --git a/packages/extension-ui/src/Popup/ImportSeed.tsx b/packages/extension-ui/src/Popup/ImportSeed.tsx
index e58f616b..0b0f04bb 100644
--- a/packages/extension-ui/src/Popup/ImportSeed.tsx
+++ b/packages/extension-ui/src/Popup/ImportSeed.tsx
@@ -70,8 +70,9 @@ export default function Import (): React.ReactElement {
 
   return (
     <>
-      <HeaderWithSmallerMargin
+      <Header
         showBackArrow
+        smallMargin
         text={t<string>('Import account')}
       />
       <div>
@@ -112,10 +113,6 @@ export default function Import (): React.ReactElement {
   );
 }
 
-const HeaderWithSmallerMargin = styled(Header)`
-  margin-bottom: 15px;
-`;
-
 const SeedInput = styled(TextAreaWithLabel)`
   margin-bottom: 16px;
   textarea {
diff --git a/packages/extension-ui/src/Popup/Signing/Request.tsx b/packages/extension-ui/src/Popup/Signing/Request.tsx
deleted file mode 100644
index 7daf94b9..00000000
--- a/packages/extension-ui/src/Popup/Signing/Request.tsx
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
-// SPDX-License-Identifier: Apache-2.0
-
-import { ExtrinsicPayload } from '@polkadot/types/interfaces';
-import { AccountJson, RequestSign } from '@polkadot/extension-base/background/types';
-import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
-
-import React, { useCallback, useEffect, useContext, useState } from 'react';
-import { PASSWORD_EXPIRY_MIN } from '@polkadot/extension-base/defaults';
-import { TypeRegistry } from '@polkadot/types';
-
-import { ActionBar, ActionContext, Address, Button, ButtonArea, Checkbox, Link, VerticalSpace } from '../../components';
-import useTranslation from '../../hooks/useTranslation';
-import { approveSignPassword, approveSignSignature, cancelSignRequest, isSignLocked } from '../../messaging';
-import Bytes from './Bytes';
-import Extrinsic from './Extrinsic';
-import Qr from './Qr';
-import Unlock from './Unlock';
-import styled from 'styled-components';
-
-interface Props {
-  account: AccountJson;
-  buttonText: string;
-  isFirst?: boolean;
-  request: RequestSign;
-  signId: string;
-  url: string;
-}
-
-interface Data {
-  hexBytes: string | null;
-  payload: ExtrinsicPayload | null;
-}
-
-// keep it global, we can and will re-use this across requests
-const registry = new TypeRegistry();
-
-function isRawPayload (payload: SignerPayloadJSON | SignerPayloadRaw): payload is SignerPayloadRaw {
-  return !!(payload as SignerPayloadRaw).data;
-}
-
-export default function Request ({ account: { isExternal }, buttonText, isFirst, request, signId, url }: Props): React.ReactElement<Props> | null {
-  const { t } = useTranslation();
-  const onAction = useContext(ActionContext);
-  const [{ hexBytes, payload }, setData] = useState<Data>({ hexBytes: null, payload: null });
-  const [error, setError] = useState<string | null>(null);
-  const [isBusy, setIsBusy] = useState(false);
-  const [isLocked, setIsLocked] = useState<boolean | null>(null);
-  const [savePass, setSavePass] = useState(false);
-  const [password, setPassword] = useState('');
-
-  useEffect(() => {
-    setIsLocked(null);
-    let timeout: number;
-
-    !isExternal && isSignLocked(signId)
-      .then(({ isLocked, remainingTime }) => {
-        setIsLocked(isLocked);
-        timeout = setTimeout(() => {
-          setIsLocked(true);
-        }, remainingTime);
-
-        // if the account was unlocked check the remember me
-        // automatically to prolong the unlock period
-        !isLocked && setSavePass(true);
-      })
-      .catch((error: Error) => console.error(error));
-
-    return () => { !!timeout && clearTimeout(timeout); };
-  }, [isExternal, signId]);
-
-  useEffect((): void => {
-    const payload = request.payload;
-
-    if (isRawPayload(payload)) {
-      setData({
-        hexBytes: payload.data,
-        payload: null
-      });
-    } else {
-      registry.setSignedExtensions(payload.signedExtensions);
-
-      setData({
-        hexBytes: null,
-        payload: registry.createType('ExtrinsicPayload', payload, { version: payload.version })
-      });
-    }
-  }, [request]);
-
-  const _onCancel = useCallback(
-    (): Promise<void> => cancelSignRequest(signId)
-      .then(() => onAction())
-      .catch((error: Error) => console.error(error)),
-    [onAction, signId]
-  );
-
-  const _onSign = useCallback(
-    (): Promise<void> => {
-      setIsBusy(true);
-
-      return approveSignPassword(signId, savePass, password)
-        .then((): void => {
-          setIsBusy(false);
-          onAction();
-        })
-        .catch((error: Error): void => {
-          setIsBusy(false);
-          setError(error.message);
-          console.error(error);
-        });
-    },
-    [onAction, password, savePass, signId]
-  );
-
-  const _onSignature = useCallback(
-    ({ signature }: { signature: string }): Promise<void> =>
-      approveSignSignature(signId, signature)
-        .then(() => onAction())
-        .catch((error: Error): void => {
-          setError(error.message);
-          console.error(error);
-        }),
-    [onAction, signId]
-  );
-
-  const RememberPasswordCheckbox = () => (
-    <Checkbox
-      checked={savePass}
-      label={ isLocked
-        ? t<string>(
-          'Remember my password for the next {{expiration}} minutes',
-          { replace: { expiration: PASSWORD_EXPIRY_MIN } }
-        )
-        : t<string>(
-          'Extend the period without password by {{expiration}} minutes',
-          { replace: { expiration: PASSWORD_EXPIRY_MIN } }
-        )
-      }
-      onChange={setSavePass}
-    />
-  );
-
-  const SignButton = () => (
-    <>
-      { isLocked && (
-        <Unlock
-          error={error}
-          isBusy={isBusy}
-          onSign={_onSign}
-          password={password}
-          setError={setError}
-          setPassword={setPassword}
-        />
-      )}
-      <RememberPasswordCheckbox />
-      <Button
-        isBusy={isBusy}
-        isDisabled={(!!isLocked && !password) || !!error}
-        onClick={_onSign}
-      >
-        {buttonText}
-      </Button>
-    </>
-  );
-
-  if (payload !== null) {
-    const json = request.payload as SignerPayloadJSON;
-
-    return (
-      <>
-        <div>
-          <Address
-            address={json.address}
-            genesisHash={json.genesisHash}
-            isExternal={isExternal}
-          />
-        </div>
-        {isExternal
-          ? (
-            <Qr
-              onSignature={_onSignature}
-              payload={payload}
-              request={json}
-            />
-          )
-          : (
-            <Extrinsic
-              isDecoded={true}
-              payload={payload}
-              request={json}
-              url={url}
-            />
-          )
-        }
-        <SignArea>
-          {isFirst && !isExternal && (
-            <SignButton/>
-          )}
-          <CancelButton>
-            <Link
-              isDanger
-              onClick={_onCancel}
-            >
-              {t<string>('Cancel')}
-            </Link>
-          </CancelButton>
-        </SignArea>
-      </>
-    );
-  } else if (hexBytes !== null) {
-    const raw = request.payload as SignerPayloadRaw;
-
-    return (
-      <>
-        <div>
-          <Address
-            address={raw.address}
-            isExternal={isExternal}
-          />
-        </div>
-        <Bytes
-          bytes={raw.data}
-          url={url}
-        />
-        <VerticalSpace />
-        <SignArea>
-          {!isExternal && <SignButton/>}
-          <CancelButton>
-            <Link
-              isDanger
-              onClick={_onCancel}
-            >
-              {t<string>('Reject')}
-            </Link>
-          </CancelButton>
-        </SignArea>
-      </>
-    );
-  }
-
-  return null;
-}
-
-const SignArea = styled(ButtonArea)`
-  flex-direction: column;
-  padding: 6px 24px;
-`;
-
-const CancelButton = styled(ActionBar)`
-  margin-top: 4px;
-  margin-bottom: 4px;
-  text-decoration: underline;
-
-  a {
-    margin: auto;
-  }
-`;
-
-CancelButton.displayName = 'CancelButton';
diff --git a/packages/extension-ui/src/Popup/Signing/Request/SignArea.tsx b/packages/extension-ui/src/Popup/Signing/Request/SignArea.tsx
new file mode 100644
index 00000000..16107495
--- /dev/null
+++ b/packages/extension-ui/src/Popup/Signing/Request/SignArea.tsx
@@ -0,0 +1,142 @@
+// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import { PASSWORD_EXPIRY_MIN } from '@polkadot/extension-base/defaults';
+import React, { useCallback, useContext, useEffect, useState } from 'react';
+import styled from 'styled-components';
+
+import { ActionBar, ActionContext, Button, ButtonArea, Checkbox, Link } from '../../../components';
+import useTranslation from '../../../hooks/useTranslation';
+import { approveSignPassword, cancelSignRequest, isSignLocked } from '../../../messaging';
+import Unlock from '../Unlock';
+
+interface Props {
+  buttonText: string;
+  className?: string;
+  error: string | null;
+  isExternal?: boolean;
+  isFirst: boolean;
+  setError: (value: string | null) => void;
+  signId: string;
+}
+
+function SignArea ({ buttonText, className, error, isExternal, isFirst, setError, signId } : Props): JSX.Element {
+  const [savePass, setSavePass] = useState(false);
+  const [isLocked, setIsLocked] = useState<boolean | null>(null);
+  const [password, setPassword] = useState('');
+  const [isBusy, setIsBusy] = useState(false);
+  const onAction = useContext(ActionContext);
+  const { t } = useTranslation();
+
+  useEffect(() => {
+    setIsLocked(null);
+    let timeout: number;
+
+    !isExternal && isSignLocked(signId)
+      .then(({ isLocked, remainingTime }) => {
+        setIsLocked(isLocked);
+        timeout = setTimeout(() => {
+          setIsLocked(true);
+        }, remainingTime);
+
+        // if the account was unlocked check the remember me
+        // automatically to prolong the unlock period
+        !isLocked && setSavePass(true);
+      })
+      .catch((error: Error) => console.error(error));
+
+    return () => { !!timeout && clearTimeout(timeout); };
+  }, [isExternal, signId]);
+
+  const _onSign = useCallback(
+    (): Promise<void> => {
+      setIsBusy(true);
+
+      return approveSignPassword(signId, savePass, password)
+        .then((): void => {
+          setIsBusy(false);
+          onAction();
+        })
+        .catch((error: Error): void => {
+          setIsBusy(false);
+          setError(error.message);
+          console.error(error);
+        });
+    },
+    [onAction, password, savePass, setError, setIsBusy, signId]
+  );
+
+  const _onCancel = useCallback(
+    (): Promise<void> => cancelSignRequest(signId)
+      .then(() => onAction())
+      .catch((error: Error) => console.error(error)),
+    [onAction, signId]
+  );
+
+  const RememberPasswordCheckbox = () => (
+    <Checkbox
+      checked={savePass}
+      label={ isLocked
+        ? t<string>(
+          'Remember my password for the next {{expiration}} minutes',
+          { replace: { expiration: PASSWORD_EXPIRY_MIN } }
+        )
+        : t<string>(
+          'Extend the period without password by {{expiration}} minutes',
+          { replace: { expiration: PASSWORD_EXPIRY_MIN } }
+        )
+      }
+      onChange={setSavePass}
+    />
+  );
+
+  return (
+    <ButtonArea className={className}>
+      {isFirst && !isExternal && (
+        <>
+          { isLocked && (
+            <Unlock
+              error={error}
+              isBusy={isBusy}
+              onSign={_onSign}
+              password={password}
+              setError={setError}
+              setPassword={setPassword}
+            />
+          )}
+          <RememberPasswordCheckbox />
+          <Button
+            isBusy={isBusy}
+            isDisabled={(!!isLocked && !password) || !!error}
+            onClick={_onSign}
+          >
+            {buttonText}
+          </Button>
+        </>
+      )}
+      <ActionBar className='cancelButton'>
+        <Link
+          isDanger
+          onClick={_onCancel}
+        >
+          {t<string>('Cancel')}
+        </Link>
+      </ActionBar>
+    </ButtonArea>
+  );
+}
+
+export default styled(SignArea)`
+  flex-direction: column;
+  padding: 6px 24px;
+
+  .cancelButton {
+    margin-top: 4px;
+    margin-bottom: 4px;
+    text-decoration: underline;
+
+    a {
+      margin: auto;
+    }
+  }
+`;
diff --git a/packages/extension-ui/src/Popup/Signing/Request/index.tsx b/packages/extension-ui/src/Popup/Signing/Request/index.tsx
new file mode 100644
index 00000000..46d549dd
--- /dev/null
+++ b/packages/extension-ui/src/Popup/Signing/Request/index.tsx
@@ -0,0 +1,141 @@
+// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import { ExtrinsicPayload } from '@polkadot/types/interfaces';
+import { AccountJson, RequestSign } from '@polkadot/extension-base/background/types';
+import { TypeRegistry } from '@polkadot/types';
+import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
+
+import React, { useCallback, useEffect, useContext, useState } from 'react';
+
+import { ActionContext, Address, VerticalSpace } from '../../../components';
+import { approveSignSignature } from '../../../messaging';
+import Bytes from '../Bytes';
+import Extrinsic from '../Extrinsic';
+import Qr from '../Qr';
+import SignArea from './SignArea';
+
+interface Props {
+  account: AccountJson;
+  buttonText: string;
+  isFirst: boolean;
+  request: RequestSign;
+  signId: string;
+  url: string;
+}
+
+interface Data {
+  hexBytes: string | null;
+  payload: ExtrinsicPayload | null;
+}
+
+// keep it global, we can and will re-use this across requests
+const registry = new TypeRegistry();
+
+function isRawPayload (payload: SignerPayloadJSON | SignerPayloadRaw): payload is SignerPayloadRaw {
+  return !!(payload as SignerPayloadRaw).data;
+}
+
+export default function Request ({ account: { isExternal }, buttonText, isFirst, request, signId, url }: Props): React.ReactElement<Props> | null {
+  const onAction = useContext(ActionContext);
+  const [{ hexBytes, payload }, setData] = useState<Data>({ hexBytes: null, payload: null });
+  const [error, setError] = useState<string | null>(null);
+
+  useEffect((): void => {
+    const payload = request.payload;
+
+    if (isRawPayload(payload)) {
+      setData({
+        hexBytes: payload.data,
+        payload: null
+      });
+    } else {
+      registry.setSignedExtensions(payload.signedExtensions);
+
+      setData({
+        hexBytes: null,
+        payload: registry.createType('ExtrinsicPayload', payload, { version: payload.version })
+      });
+    }
+  }, [request]);
+
+  const _onSignature = useCallback(
+    ({ signature }: { signature: string }): Promise<void> =>
+      approveSignSignature(signId, signature)
+        .then(() => onAction())
+        .catch((error: Error): void => {
+          setError(error.message);
+          console.error(error);
+        }),
+    [onAction, signId]
+  );
+
+  if (payload !== null) {
+    const json = request.payload as SignerPayloadJSON;
+
+    return (
+      <>
+        <div>
+          <Address
+            address={json.address}
+            genesisHash={json.genesisHash}
+            isExternal={isExternal}
+          />
+        </div>
+        {isExternal
+          ? (
+            <Qr
+              onSignature={_onSignature}
+              payload={payload}
+              request={json}
+            />
+          )
+          : (
+            <Extrinsic
+              isDecoded={true}
+              payload={payload}
+              request={json}
+              url={url}
+            />
+          )
+        }
+        <SignArea
+          buttonText={buttonText}
+          error={error}
+          isExternal={isExternal}
+          isFirst={isFirst}
+          setError={setError}
+          signId={signId}
+        />
+      </>
+    );
+  } else if (hexBytes !== null) {
+    const raw = request.payload as SignerPayloadRaw;
+
+    return (
+      <>
+        <div>
+          <Address
+            address={raw.address}
+            isExternal={isExternal}
+          />
+        </div>
+        <Bytes
+          bytes={raw.data}
+          url={url}
+        />
+        <VerticalSpace />
+        <SignArea
+          buttonText={buttonText}
+          error={error}
+          isExternal={isExternal}
+          isFirst={isFirst}
+          setError={setError}
+          signId={signId}
+        />
+      </>
+    );
+  }
+
+  return null;
+}
diff --git a/packages/extension-ui/src/Popup/Welcome.tsx b/packages/extension-ui/src/Popup/Welcome.tsx
index 99d1fee3..e884960d 100644
--- a/packages/extension-ui/src/Popup/Welcome.tsx
+++ b/packages/extension-ui/src/Popup/Welcome.tsx
@@ -1,7 +1,7 @@
 // Copyright 2019-2020 @polkadot/extension-ui authors & contributors
 // SPDX-License-Identifier: Apache-2.0
 
-import { ThemeProps as Props } from '../types';
+import { ThemeProps } from '../types';
 
 import React, { useContext } from 'react';
 import styled from 'styled-components';
@@ -10,7 +10,11 @@ import { ActionContext, Box, Button, ButtonArea, List, VerticalSpace } from '../
 import useTranslation from '../hooks/useTranslation';
 import { Header } from '../partials';
 
-export default function Welcome (): React.ReactElement<Props> {
+interface Props extends ThemeProps {
+  className?: string;
+}
+
+const Welcome = function ({ className }: Props): React.ReactElement<Props> {
   const { t } = useTranslation();
   const onAction = useContext(ActionContext);
 
@@ -22,31 +26,29 @@ export default function Welcome (): React.ReactElement<Props> {
   return (
     <>
       <Header text={t<string>('Welcome')} />
-      <Note>{t<string>('Before we start, just a couple of notes regarding use:')}</Note>
-      <TextBox>
-        <List>
-          <li>{t<string>('We do not send any clicks, pageviews or events to a central server')}</li>
-          <li>{t<string>('We do not use any trackers or analytics')}</li>
-          <li>{t<string>("We don't collect keys, addresses or any information - your information never leaves this machine")}</li>
-        </List>
-      </TextBox>
-      <Note>{t<string>('... we are not in the information collection business (even anonymized).')}</Note>
+      <div className={className}>
+        <p>{t<string>('Before we start, just a couple of notes regarding use:')}</p>
+        <Box>
+          <List>
+            <li>{t<string>('We do not send any clicks, pageviews or events to a central server')}</li>
+            <li>{t<string>('We do not use any trackers or analytics')}</li>
+            <li>{t<string>("We don't collect keys, addresses or any information - your information never leaves this machine")}</li>
+          </List>
+        </Box>
+        <p>{t<string>('... we are not in the information collection business (even anonymized).')}</p>
+      </div>
       <VerticalSpace />
       <ButtonArea>
         <Button onClick={_onClick}>{t<string>('Understood, let me continue')}</Button>
       </ButtonArea>
     </>
   );
-}
-
-const Note = styled.p(({ theme }: Props) => `
-  color: ${theme.subTextColor};
-  margin-bottom: 6px;
-  margin-top: 0;
-`);
-
-const TextBox = styled(Box)(({ theme }: Props) => `
-  border: 1px solid ${theme.inputBorderColor};
-  color: ${theme.subTextColor};
-  margin: 0.75rem 24px;
+};
+
+export default styled(Welcome)(({ theme }: Props) => `
+  p {
+    color: ${theme.subTextColor};
+    margin-bottom: 6px;
+    margin-top: 0;
+  }
 `);
diff --git a/packages/extension-ui/src/components/Address.tsx b/packages/extension-ui/src/components/Address.tsx
index 0c2980d0..373672a7 100644
--- a/packages/extension-ui/src/components/Address.tsx
+++ b/packages/extension-ui/src/components/Address.tsx
@@ -186,6 +186,7 @@ function Address ({ actions, address, children, className, genesisHash, isExtern
             </div>
             <CopyToClipboard text={(formatted && formatted) || ''} >
               <FontAwesomeIcon
+                className='copyIcon'
                 icon={faCopy}
                 onClick={_onCopy}
                 size='sm'
diff --git a/packages/extension-ui/src/components/Box.tsx b/packages/extension-ui/src/components/Box.tsx
index 971362df..dcd55c78 100644
--- a/packages/extension-ui/src/components/Box.tsx
+++ b/packages/extension-ui/src/components/Box.tsx
@@ -23,12 +23,12 @@ function Box ({ banner, children, className }: Props): React.ReactElement<Props>
 
 export default styled(Box)(({ theme }: ThemeProps) => `
   background: ${theme.readonlyInputBackground};
-  border: none;
+  border: 1px solid ${theme.inputBorderColor};
   border-radius: ${theme.borderRadius};
-  color: ${theme.textColor};
+  color: ${theme.subTextColor};
   font-family: ${theme.fontFamily};
   font-size: ${theme.fontSize};
-  margin: ${theme.boxMargin};
+  margin: 0.75rem 24px;
   padding: ${theme.boxPadding};
   position: relative;
 
diff --git a/packages/extension-ui/src/components/ButtonArea.tsx b/packages/extension-ui/src/components/ButtonArea.tsx
index ef39931d..97f4ec26 100644
--- a/packages/extension-ui/src/components/ButtonArea.tsx
+++ b/packages/extension-ui/src/components/ButtonArea.tsx
@@ -3,22 +3,32 @@
 
 import { ThemeProps } from '../types';
 
+import React from 'react';
 import styled from 'styled-components';
-import Button from './Button';
 
-export default styled.div(({ theme }: ThemeProps) => `
+interface Props extends ThemeProps {
+  className?: string;
+  children: React.ReactNode;
+}
+
+const ButtonArea = function ({ children, className }: Props) {
+  return (
+    <div className={className}>
+      {children}
+    </div>
+  );
+};
+
+export default styled(ButtonArea)(({ theme }: ThemeProps) => `
   display: flex;
   flex-direction: row;
   background: ${theme.highlightedAreaBackground};
   border-top: 1px solid ${theme.inputBorderColor};
   padding: 12px 24px;
+  margin-left: 0;
+  margin-right: 0;
 
-  &&& {
-    margin-left: 0;
-    margin-right: 0;
-  }
-
-  & > ${Button}:not(:last-of-type) {
+  & > button:not(:last-of-type) {
     margin-right: 8px;
   }
 `);
diff --git a/packages/extension-ui/src/components/List.tsx b/packages/extension-ui/src/components/List.tsx
index c0c732b2..bdc5efc9 100644
--- a/packages/extension-ui/src/components/List.tsx
+++ b/packages/extension-ui/src/components/List.tsx
@@ -1,11 +1,24 @@
 // Copyright 2019-2020 @polkadot/extension-ui authors & contributors
 // SPDX-License-Identifier: Apache-2.0
 
+import React from 'react';
+
 import { ThemeProps } from '../types';
 
 import styled from 'styled-components';
 
-export default styled.ul(({ theme }: ThemeProps) => `
+interface Props extends ThemeProps {
+  className?: string;
+  children: React.ReactNode;
+}
+
+const List = ({ children, className }: Props) => (
+  <ul className={className}>
+    {children}
+  </ul>
+);
+
+export default styled(List)(({ theme }: ThemeProps) => `
   list-style: none;
   padding-inline-start: 10px;
   padding-inline-end: 10px;
diff --git a/packages/extension-ui/src/components/Main.tsx b/packages/extension-ui/src/components/Main.tsx
new file mode 100644
index 00000000..00423878
--- /dev/null
+++ b/packages/extension-ui/src/components/Main.tsx
@@ -0,0 +1,40 @@
+// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import { ThemeProps } from '../types';
+
+import React from 'react';
+import styled from 'styled-components';
+
+interface Props {
+  children: React.ReactNode;
+  className?: string;
+}
+
+function Main ({ children, className }: Props): React.ReactElement<Props> {
+  return (
+    <main className={className}>
+      {children}
+    </main>
+  );
+}
+
+export default styled(Main)(({ theme }: ThemeProps) => `
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  background: ${theme.background};
+  color: ${theme.textColor};
+  font-size: ${theme.fontSize};
+  line-height: ${theme.lineHeight};
+  border: 1px solid ${theme.inputBorderColor};
+
+  * {
+    font-family: ${theme.fontFamily};
+  }
+
+  > * {
+    padding-left: 24px;
+    padding-right: 24px;
+  }
+`);
diff --git a/packages/extension-ui/src/components/MenuItem.tsx b/packages/extension-ui/src/components/MenuItem.tsx
index 7a3ac710..a3c43f7c 100644
--- a/packages/extension-ui/src/components/MenuItem.tsx
+++ b/packages/extension-ui/src/components/MenuItem.tsx
@@ -4,9 +4,9 @@
 import React from 'react';
 import styled from 'styled-components';
 
-import Title from './Title';
+import { ThemeProps } from '../types';
 
-interface Props {
+interface Props extends ThemeProps {
   children: React.ReactNode;
   className?: string;
   noBorder?: boolean;
@@ -17,23 +17,27 @@ function MenuItem ({ children, className = '', title }: Props): React.ReactEleme
   return (
     <div className={className}>
       {title && (
-        <Title className='itemTitle'>{title}</Title>
+        <div className='itemTitle'>{title}</div>
       )}
       {children}
     </div>
   );
 }
 
-export default styled(MenuItem)`
+export default styled(MenuItem)(({ theme }: ThemeProps) => `
   min-width: 13rem;
   padding: 0 16px;
   max-width: 100%;
 
   > .itemTitle {
-    margin: 0;
-  }
-
-  &+& {
-    margin-top: 16px;
-  }
-`;
+      margin: 0;
+      width: 100%;
+      font-weight: 800;
+      font-size: 10px;
+      line-height: 14px;
+      letter-spacing: 0.04em;
+      text-transform: uppercase;
+      color: ${theme.textColor};
+      opacity: 0.65;
+    }
+`);
diff --git a/packages/extension-ui/src/components/Svg.tsx b/packages/extension-ui/src/components/Svg.tsx
index 27f0b957..1749d3dc 100644
--- a/packages/extension-ui/src/components/Svg.tsx
+++ b/packages/extension-ui/src/components/Svg.tsx
@@ -1,15 +1,20 @@
 // Copyright 2019-2020 @polkadot/extension-ui authors & contributors
 // SPDX-License-Identifier: Apache-2.0
 
+import React from 'react';
+
 import { ThemeProps } from '../types';
 
 import styled from 'styled-components';
 
-interface Props {
+interface Props extends ThemeProps {
+  className?: string;
   src: string;
 }
 
-export default styled.span<Props>(({ src, theme }: Props & ThemeProps) => `
+const Svg = ({ className }: Props) => <span className={className} />;
+
+export default styled(Svg)(({ src, theme }: Props) => `
   background: ${theme.textColor};
   display: inline-block;
   mask: url(${src});
diff --git a/packages/extension-ui/src/components/Title.ts b/packages/extension-ui/src/components/Title.ts
deleted file mode 100644
index ed5b9f5e..00000000
--- a/packages/extension-ui/src/components/Title.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
-// SPDX-License-Identifier: Apache-2.0
-
-import { ThemeProps } from '../types';
-
-import styled from 'styled-components';
-
-export default styled.div(({ theme }: ThemeProps) => `
-  width: 100%;
-  margin-bottom: 8px;
-  margin-top: 18px;
-  font-weight: 800;
-  font-size: 10px;
-  line-height: 14px;
-  letter-spacing: 0.04em;
-  text-transform: uppercase;
-  color: ${theme.textColor};
-  opacity: 0.65;
-`);
diff --git a/packages/extension-ui/src/components/ValidatedInput.tsx b/packages/extension-ui/src/components/ValidatedInput.tsx
index c0374edc..6a0b5f7a 100644
--- a/packages/extension-ui/src/components/ValidatedInput.tsx
+++ b/packages/extension-ui/src/components/ValidatedInput.tsx
@@ -50,19 +50,17 @@ function ValidatedInput<T extends Record<string, unknown>> ({ className, compone
         onChange={setValue}
         value={value}
       />
-      {Result.isError(validationResult) && <ErrorMessage>{validationResult.error.errorDescription}</ErrorMessage>}
+      {Result.isError(validationResult) && <span className='error'>{validationResult.error.errorDescription}</span>}
     </div>
   );
 }
 
-const ErrorMessage = styled.span`
-  display: block;
-  margin-top: -10px;
-  font-size: ${({ theme }: ThemeProps): string => theme.labelFontSize};
-  line-height: ${({ theme }: ThemeProps): string => theme.labelLineHeight};
-  color: ${({ theme }: ThemeProps): string => theme.errorColor};
-`;
-
-ErrorMessage.displayName = 'ErrorMessage';
-
-export default ValidatedInput;
+export default styled(ValidatedInput)(({ theme }: ThemeProps) => `
+  .error {
+    display: block;
+    margin-top: -10px;
+    font-size: ${theme.labelFontSize};
+    line-height: ${theme.labelLineHeight};
+    color: ${theme.errorColor};
+  }
+`);
diff --git a/packages/extension-ui/src/components/VerticalSpace.ts b/packages/extension-ui/src/components/VerticalSpace.ts
deleted file mode 100644
index df1a1837..00000000
--- a/packages/extension-ui/src/components/VerticalSpace.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
-// SPDX-License-Identifier: Apache-2.0
-
-import styled from 'styled-components';
-
-export default styled.div`
-  height: 100%;
-`;
diff --git a/packages/extension-ui/src/components/VerticalSpace.tsx b/packages/extension-ui/src/components/VerticalSpace.tsx
new file mode 100644
index 00000000..a0edae0c
--- /dev/null
+++ b/packages/extension-ui/src/components/VerticalSpace.tsx
@@ -0,0 +1,18 @@
+// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import React from 'react';
+
+import styled from 'styled-components';
+
+interface Props {
+  className?: string;
+}
+
+function VerticalSpace ({ className }: Props): React.ReactElement<Props> {
+  return <div className={className}/>;
+}
+
+export default styled(VerticalSpace)`
+  height: 100%;
+`;
diff --git a/packages/extension-ui/src/components/View.tsx b/packages/extension-ui/src/components/View.tsx
index 15807200..fae0c0fa 100644
--- a/packages/extension-ui/src/components/View.tsx
+++ b/packages/extension-ui/src/components/View.tsx
@@ -4,10 +4,10 @@
 import { ThemeProps } from '../types';
 
 import React, { useState } from 'react';
-import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
+import { ThemeProvider, createGlobalStyle } from 'styled-components';
 
 // FIXME We should not import from index when this one is imported there as well
-import { AvailableThemes, chooseTheme, themes, ThemeSwitchContext } from '.';
+import { AvailableThemes, chooseTheme, Main, themes, ThemeSwitchContext } from '.';
 
 interface Props {
   children: React.ReactNode;
@@ -49,24 +49,4 @@ const BodyTheme = createGlobalStyle<ThemeProps>`
   }
 `;
 
-const Main = styled.main`
-  display: flex;
-  flex-direction: column;
-  height: 100%;
-  background: ${({ theme }: ThemeProps): string => theme.background};
-  color: ${({ theme }: ThemeProps): string => theme.textColor};
-  font-size: ${({ theme }: ThemeProps): string => theme.fontSize};
-  line-height: ${({ theme }: ThemeProps): string => theme.lineHeight};
-  border: 1px solid ${({ theme }: ThemeProps): string => theme.inputBorderColor};
-
-  * {
-    font-family: ${({ theme }: ThemeProps): string => theme.fontFamily};
-  }
-
-  > * {
-    padding-left: 24px;
-    padding-right: 24px;
-  }
-`;
-
 export default View;
diff --git a/packages/extension-ui/src/components/index.ts b/packages/extension-ui/src/components/index.ts
index c95db831..16a0b73d 100644
--- a/packages/extension-ui/src/components/index.ts
+++ b/packages/extension-ui/src/components/index.ts
@@ -21,6 +21,7 @@ export { default as Label } from './Label';
 export { default as Link } from './Link';
 export { default as List } from './List';
 export { default as Loading } from './Loading';
+export { default as Main } from './Main';
 export { default as Menu } from './Menu';
 export { default as MenuDivider } from './MenuDivider';
 export { default as MenuItem } from './MenuItem';
@@ -31,7 +32,6 @@ export { default as Svg } from './Svg';
 export { default as Switch } from './Switch';
 export { default as Table } from './Table';
 export { default as TextAreaWithLabel } from './TextAreaWithLabel';
-export { default as Title } from './Title';
 export { default as ValidatedInput } from './ValidatedInput';
 export { default as VerticalSpace } from './VerticalSpace';
 export { default as View } from './View';
diff --git a/packages/extension-ui/src/partials/Header.tsx b/packages/extension-ui/src/partials/Header.tsx
index c898586f..5eb73c58 100644
--- a/packages/extension-ui/src/partials/Header.tsx
+++ b/packages/extension-ui/src/partials/Header.tsx
@@ -20,10 +20,11 @@ interface Props extends ThemeProps {
   showAdd?: boolean;
   showBackArrow?: boolean;
   showSettings?: boolean;
+  smallMargin?: boolean;
   text?: React.ReactNode;
 }
 
-function Header ({ children, className, showAdd, showBackArrow, showSettings, text }: Props): React.ReactElement<Props> {
+function Header ({ children, className = '', showAdd, showBackArrow, showSettings, smallMargin = false, text }: Props): React.ReactElement<Props> {
   const [isAddOpen, setShowAdd] = useState(false);
   const [isSettingsOpen, setShowSettings] = useState(false);
   const addRef = useRef(null);
@@ -48,7 +49,7 @@ function Header ({ children, className, showAdd, showBackArrow, showSettings, te
   );
 
   return (
-    <div className={className}>
+    <div className={`${className} ${smallMargin ? 'smallMargin' : ''}`}>
       <div className='container'>
         <div className='branding'>
           {showBackArrow
@@ -197,6 +198,9 @@ export default React.memo(styled(Header)(({ theme }: Props) => `
     &:visited {
       color: ${theme.labelColor};
     }
+  }
 
+  &.smallMargin {
+    margin-bottom: 15px;
   }
 `));


Test patch:
diff --git a/packages/extension-ui/src/Popup/Authorize/Authorize.test.tsx b/packages/extension-ui/src/Popup/Authorize/Authorize.test.tsx
index ff2a23e8..9eb079d7 100644
--- a/packages/extension-ui/src/Popup/Authorize/Authorize.test.tsx
+++ b/packages/extension-ui/src/Popup/Authorize/Authorize.test.tsx
@@ -47,8 +47,9 @@ describe('Authorize', () => {
       { id: '2', request: { origin: 'abc' }, url: 'http://polkadot.pl' }
     ]);
 
-    expect(wrapper.find('AcceptButton').length).toBe(1);
+    expect(wrapper.find(Request).length).toBe(2);
     expect(wrapper.find(Icon).length).toBe(2);
     expect(wrapper.find(Request).at(1).find('.tab-info').text()).toBe('An application, self-identifying as abc is requesting access from http://polkadot.pl.');
+    expect(wrapper.find('button.acceptButton').length).toBe(1);
   });
 });
diff --git a/packages/extension-ui/src/Popup/CreateAccount/CreateAccount.test.tsx b/packages/extension-ui/src/Popup/CreateAccount/CreateAccount.test.tsx
index 472a75b6..ce216789 100644
--- a/packages/extension-ui/src/Popup/CreateAccount/CreateAccount.test.tsx
+++ b/packages/extension-ui/src/Popup/CreateAccount/CreateAccount.test.tsx
@@ -101,7 +101,7 @@ describe('Create Account', () => {
     it('after typing less than 3 characters into name input, password input is not visible', async () => {
       await enterName('ab');
       expect(wrapper.find(InputWithLabel).find('[data-input-name]').find(Input).prop('withError')).toBe(true);
-      expect(wrapper.find('ErrorMessage').first().text()).toBe('Account name is too short');
+      expect(wrapper.find('span.error').first().text()).toBe('Account name is too short');
       expect(wrapper.find(InputWithLabel).find('[data-input-password]').find(Input)).toHaveLength(1);
       expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]')).toHaveLength(0);
       expect(wrapper.find('[data-button-action="add new root"] button').prop('disabled')).toBe(true);
@@ -124,7 +124,7 @@ describe('Create Account', () => {
     it('password shorter than 6 characters should be not valid', async () => {
       await enterName('abc').then(password('abcde'));
       expect(wrapper.find(InputWithLabel).find('[data-input-password]').find(Input).prop('withError')).toBe(true);
-      expect(wrapper.find('ErrorMessage').text()).toBe('Password is too short');
+      expect(wrapper.find('span.error').text()).toBe('Password is too short');
       expect(wrapper.find(InputWithLabel).find('[data-input-password]').find(Input)).toHaveLength(1);
       expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]')).toHaveLength(0);
       expect(wrapper.find('[data-button-action="add new root"] button').prop('disabled')).toBe(true);
@@ -132,14 +132,14 @@ describe('Create Account', () => {
 
     it('submit button is not visible until both passwords are equal', async () => {
       await enterName('abc').then(password('abcdef')).then(repeat('abcdeg'));
-      expect(wrapper.find('ErrorMessage').text()).toBe('Passwords do not match');
+      expect(wrapper.find('span.error').text()).toBe('Passwords do not match');
       expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]').find(Input).prop('withError')).toBe(true);
       expect(wrapper.find('[data-button-action="add new root"] button').prop('disabled')).toBe(true);
     });
 
     it('submit button is visible when both passwords are equal', async () => {
       await enterName('abc').then(password('abcdef')).then(repeat('abcdef'));
-      expect(wrapper.find('ErrorMessage')).toHaveLength(0);
+      expect(wrapper.find('span.error')).toHaveLength(0);
       expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]').find(Input).prop('withError')).toBe(false);
       expect(wrapper.find('[data-button-action="add new root"] button').prop('disabled')).toBe(false);
     });
@@ -170,7 +170,7 @@ describe('Create Account', () => {
 
     it('first password changes - button is not disabled', async () => {
       await type(wrapper.find('input[type="password"]').first(), 'aaaaaa');
-      expect(wrapper.find('ErrorMessage').text()).toBe('Passwords do not match');
+      expect(wrapper.find('span.error').text()).toBe('Passwords do not match');
       expect(wrapper.find('[data-button-action="add new root"] button').prop('disabled')).toBe(true);
     });