diff --git a/src/__tests__/__snapshots__/index.spec.tsx.snap b/src/__tests__/__snapshots__/index.spec.tsx.snap
index cec9e77e..6837c649 100644
--- a/src/__tests__/__snapshots__/index.spec.tsx.snap
+++ b/src/__tests__/__snapshots__/index.spec.tsx.snap
@@ -258,6 +258,7 @@ exports[`HTML Output given anyOf combiner placed next to allOf given allOf mergi
                 
type
                 string
               
+              
               required
             
             
@@ -425,6 +426,7 @@ exports[`HTML Output given oneOf combiner placed next to allOf given allOf mergi
                 
type
                 string
               
@@ -548,6 +550,7 @@ exports[`HTML Output given standalone mode, should populate proper nodes 1`] = `
                 
id
                 string
               description
                 string
               
+              
               write-only
             
           
diff --git a/src/components/JsonSchemaViewer.tsx b/src/components/JsonSchemaViewer.tsx
index 9247d425..b35409e6 100644
--- a/src/components/JsonSchemaViewer.tsx
+++ b/src/components/JsonSchemaViewer.tsx
@@ -14,8 +14,9 @@ import * as React from 'react';
 
 import { JSVOptions, JSVOptionsContextProvider } from '../contexts';
 import type { JSONSchema } from '../types';
-import { PathCrumbs, pathCrumbsAtom } from './PathCrumbs';
+import { PathCrumbs } from './PathCrumbs';
 import { TopLevelSchemaRow } from './SchemaRow';
+import { hoveredNodeAtom } from './SchemaRow/state';
 
 export type JsonSchemaProps = Partial & {
   schema: JSONSchema;
@@ -74,10 +75,10 @@ const JsonSchemaViewerInner = ({
   JsonSchemaProps,
   'schema' | 'viewMode' | 'className' | 'resolveRef' | 'emptyText' | 'onTreePopulated' | 'maxHeight' | 'parentCrumbs'
 >) => {
-  const setPathCrumbs = useUpdateAtom(pathCrumbsAtom);
+  const setHoveredNode = useUpdateAtom(hoveredNodeAtom);
   const onMouseLeave = React.useCallback(() => {
-    setPathCrumbs([]);
-  }, [setPathCrumbs]);
+    setHoveredNode(null);
+  }, [setHoveredNode]);
 
   const { jsonSchemaTreeRoot, nodeCount } = React.useMemo(() => {
     const jsonSchemaTree = new JsonSchemaTree(schema, {
diff --git a/src/components/PathCrumbs/index.tsx b/src/components/PathCrumbs/index.tsx
index 8d5f2590..6930ddf3 100644
--- a/src/components/PathCrumbs/index.tsx
+++ b/src/components/PathCrumbs/index.tsx
@@ -1,15 +1,9 @@
-import { isRegularNode, isRootNode, SchemaNode } from '@stoplight/json-schema-tree';
 import { Box, HStack } from '@stoplight/mosaic';
-import { atom, useAtom } from 'jotai';
+import { useAtom } from 'jotai';
 import * as React from 'react';
 
 import { useJSVOptionsContext } from '../../contexts';
-
-export const showPathCrumbsAtom = atom(false);
-
-export const pathCrumbsAtom = atom([], (_get, set, node) => {
-  set(pathCrumbsAtom, propertyPathToObjectPath(node as SchemaNode));
-});
+import { pathCrumbsAtom, showPathCrumbsAtom } from './state';
 
 export const PathCrumbs = ({ parentCrumbs = [] }: { parentCrumbs?: string[] }) => {
   const [showPathCrumbs] = useAtom(showPathCrumbsAtom);
@@ -67,32 +61,3 @@ export const PathCrumbs = ({ parentCrumbs = [] }: { parentCrumbs?: string[] }) =
     
   );
 };
-
-function propertyPathToObjectPath(node: SchemaNode) {
-  const objectPath: string[] = [];
-
-  let currentNode: SchemaNode | null = node;
-  while (currentNode && !isRootNode(currentNode)) {
-    if (isRegularNode(currentNode)) {
-      const pathPart = currentNode.subpath[currentNode.subpath.length - 1];
-
-      if (currentNode.primaryType === 'array') {
-        const key = `${pathPart || ''}[]`;
-        if (objectPath[objectPath.length - 1]) {
-          objectPath[objectPath.length - 1] = key;
-        } else {
-          objectPath.push(key);
-        }
-      } else if (
-        pathPart &&
-        (currentNode.subpath.length !== 2 || !['allOf', 'oneOf', 'anyOf'].includes(currentNode.subpath[0]))
-      ) {
-        objectPath.push(currentNode.subpath[currentNode.subpath.length - 1]);
-      }
-    }
-
-    currentNode = currentNode.parent;
-  }
-
-  return objectPath.reverse();
-}
diff --git a/src/components/PathCrumbs/state.ts b/src/components/PathCrumbs/state.ts
new file mode 100644
index 00000000..8545b9e5
--- /dev/null
+++ b/src/components/PathCrumbs/state.ts
@@ -0,0 +1,43 @@
+import { isRegularNode, isRootNode, SchemaNode } from '@stoplight/json-schema-tree';
+import { atom } from 'jotai';
+
+import { hoveredNodeAtom } from '../SchemaRow/state';
+
+export const showPathCrumbsAtom = atom(false);
+
+export const pathCrumbsAtom = atom(get => {
+  const node = get(hoveredNodeAtom);
+
+  if (!node) return [];
+
+  return propertyPathToObjectPath(node as SchemaNode);
+});
+
+function propertyPathToObjectPath(node: SchemaNode) {
+  const objectPath: string[] = [];
+
+  let currentNode: SchemaNode | null = node;
+  while (currentNode && !isRootNode(currentNode)) {
+    if (isRegularNode(currentNode)) {
+      const pathPart = currentNode.subpath[currentNode.subpath.length - 1];
+
+      if (currentNode.primaryType === 'array') {
+        const key = `${pathPart || ''}[]`;
+        if (objectPath[objectPath.length - 1]) {
+          objectPath[objectPath.length - 1] = key;
+        } else {
+          objectPath.push(key);
+        }
+      } else if (
+        pathPart &&
+        (currentNode.subpath.length !== 2 || !['allOf', 'oneOf', 'anyOf'].includes(currentNode.subpath[0]))
+      ) {
+        objectPath.push(currentNode.subpath[currentNode.subpath.length - 1]);
+      }
+    }
+
+    currentNode = currentNode.parent;
+  }
+
+  return objectPath.reverse();
+}
diff --git a/src/components/SchemaRow/SchemaRow.tsx b/src/components/SchemaRow/SchemaRow.tsx
index 770545d2..180ed8d2 100644
--- a/src/components/SchemaRow/SchemaRow.tsx
+++ b/src/components/SchemaRow/SchemaRow.tsx
@@ -7,29 +7,32 @@ import {
   SchemaNode,
   SchemaNodeKind,
 } from '@stoplight/json-schema-tree';
-import { Box, Flex, Icon, Select, VStack } from '@stoplight/mosaic';
-import { useUpdateAtom } from 'jotai/utils';
+import { Box, Flex, Icon, Select, SpaceVals, VStack } from '@stoplight/mosaic';
+import { useAtomValue, useUpdateAtom } from 'jotai/utils';
 import last from 'lodash/last.js';
 import * as React from 'react';
 
 import { COMBINER_NAME_MAP } from '../../consts';
 import { useJSVOptionsContext } from '../../contexts';
 import { calculateChildrenToShow, isFlattenableNode, isPropertyRequired } from '../../tree';
-import { pathCrumbsAtom } from '../PathCrumbs';
 import { Caret, Description, Format, getValidationsFromSchema, Types, Validations } from '../shared';
 import { ChildStack } from '../shared/ChildStack';
-import { Properties } from '../shared/Properties';
+import { Properties, useHasProperties } from '../shared/Properties';
+import { hoveredNodeAtom, isNodeHoveredAtom } from './state';
 import { useChoices } from './useChoices';
 
 export interface SchemaRowProps {
   schemaNode: SchemaNode;
   nestingLevel: number;
+  pl?: SpaceVals;
 }
 
-export const SchemaRow: React.FunctionComponent = ({ schemaNode, nestingLevel }) => {
+export const SchemaRow: React.FunctionComponent = React.memo(({ schemaNode, nestingLevel, pl }) => {
   const { defaultExpandedDepth, renderRowAddon, onGoToRef, hideExamples, renderRootTreeLines } = useJSVOptionsContext();
 
-  const setPathCrumbs = useUpdateAtom(pathCrumbsAtom);
+  const setHoveredNode = useUpdateAtom(hoveredNodeAtom);
+  const isHovering = useAtomValue(isNodeHoveredAtom(schemaNode));
+
   const [isExpanded, setExpanded] = React.useState(
     !isMirroredNode(schemaNode) && nestingLevel <= defaultExpandedDepth,
   );
@@ -62,13 +65,20 @@ export const SchemaRow: React.FunctionComponent = ({ schemaNode,
   const isCollapsible = childNodes.length > 0;
   const isRootLevel = nestingLevel < rootLevel;
 
+  const required = isPropertyRequired(schemaNode);
+  const deprecated = isRegularNode(schemaNode) && schemaNode.deprecated;
+  const validations = isRegularNode(schemaNode) ? schemaNode.validations : {};
+  const hasProperties = useHasProperties({ required, deprecated, validations });
+
   return (
     <>
        {
           e.stopPropagation();
-          setPathCrumbs(selectedChoice.type);
+          setHoveredNode(selectedChoice.type);
         }}
       >
         {!isRootLevel && }
@@ -82,7 +92,7 @@ export const SchemaRow: React.FunctionComponent = ({ schemaNode,
           >
             {isCollapsible ?  : null}
 
-            
+            
               {schemaNode.subpath.length > 0 && shouldShowPropertyName(schemaNode) && (
                 
                   {last(schemaNode.subpath)}
@@ -136,11 +146,9 @@ export const SchemaRow: React.FunctionComponent = ({ schemaNode,
               )}
             
 
-            
+            {hasProperties && }
+
+            
           
 
           {typeof description === 'string' && description.length > 0 && }
@@ -159,10 +167,12 @@ export const SchemaRow: React.FunctionComponent = ({ schemaNode,
         {renderRowAddon ? {renderRowAddon({ schemaNode, nestingLevel })} : null}
       
 
-      {isCollapsible && isExpanded ?  : null}
+      {isCollapsible && isExpanded ? (
+        
+      ) : null}
     >
   );
-};
+});
 
 function shouldShowPropertyName(schemaNode: SchemaNode) {
   return (
diff --git a/src/components/SchemaRow/TopLevelSchemaRow.tsx b/src/components/SchemaRow/TopLevelSchemaRow.tsx
index 29455c65..ef1b2a1a 100644
--- a/src/components/SchemaRow/TopLevelSchemaRow.tsx
+++ b/src/components/SchemaRow/TopLevelSchemaRow.tsx
@@ -7,7 +7,7 @@ import * as React from 'react';
 import { COMBINER_NAME_MAP } from '../../consts';
 import { useIsOnScreen } from '../../hooks/useIsOnScreen';
 import { calculateChildrenToShow, isComplexArray } from '../../tree';
-import { showPathCrumbsAtom } from '../PathCrumbs';
+import { showPathCrumbsAtom } from '../PathCrumbs/state';
 import { ChildStack } from '../shared/ChildStack';
 import { SchemaRow, SchemaRowProps } from './SchemaRow';
 import { useChoices } from './useChoices';
@@ -22,7 +22,7 @@ export const TopLevelSchemaRow = ({ schemaNode }: Pick
         
-        
+        
       >
     );
   }
@@ -63,7 +63,9 @@ export const TopLevelSchemaRow = ({ schemaNode }: Pick
 
-        {childNodes.length > 0 ?  : null}
+        {childNodes.length > 0 ? (
+          
+        ) : null}
       >
     );
   }
@@ -77,7 +79,9 @@ export const TopLevelSchemaRow = ({ schemaNode }: Pick
 
-        {childNodes.length > 0 ?  : null}
+        {childNodes.length > 0 ? (
+          
+        ) : null}
       >
     );
   }
diff --git a/src/components/SchemaRow/state.ts b/src/components/SchemaRow/state.ts
new file mode 100644
index 00000000..6a389649
--- /dev/null
+++ b/src/components/SchemaRow/state.ts
@@ -0,0 +1,15 @@
+import { SchemaNode } from '@stoplight/json-schema-tree';
+import { atom } from 'jotai';
+import { atomFamily } from 'jotai/utils';
+
+export const hoveredNodeAtom = atom(null);
+export const isNodeHoveredAtom = atomFamily((node: SchemaNode) => atom(get => node === get(hoveredNodeAtom)));
+export const isChildNodeHoveredAtom = atomFamily((parent: SchemaNode) =>
+  atom(get => {
+    const hoveredNode = get(hoveredNodeAtom);
+
+    if (!hoveredNode || hoveredNode === parent) return false;
+
+    return hoveredNode.parent === parent;
+  }),
+);
diff --git a/src/components/shared/ChildStack.tsx b/src/components/shared/ChildStack.tsx
index e72403f0..bc186b8c 100644
--- a/src/components/shared/ChildStack.tsx
+++ b/src/components/shared/ChildStack.tsx
@@ -1,5 +1,5 @@
 import { SchemaNode } from '@stoplight/json-schema-tree';
-import { SpaceVals, VStack } from '@stoplight/mosaic';
+import { Box, SpaceVals } from '@stoplight/mosaic';
 import * as React from 'react';
 
 import { NESTING_OFFSET } from '../../consts';
@@ -7,40 +7,41 @@ import { useJSVOptionsContext } from '../../contexts';
 import { SchemaRow, SchemaRowProps } from '../SchemaRow';
 
 type ChildStackProps = {
+  schemaNode: SchemaNode;
   childNodes: readonly SchemaNode[];
   currentNestingLevel: number;
   className?: string;
   RowComponent?: React.FC;
 };
 
-export const ChildStack = ({
-  childNodes,
-  currentNestingLevel,
-  className,
-  RowComponent = SchemaRow,
-}: ChildStackProps) => {
-  const { renderRootTreeLines } = useJSVOptionsContext();
-  const rootLevel = renderRootTreeLines ? 0 : 1;
-  const isRootLevel = currentNestingLevel < rootLevel;
+export const ChildStack = React.memo(
+  ({ childNodes, currentNestingLevel, className, RowComponent = SchemaRow }: ChildStackProps) => {
+    const { renderRootTreeLines } = useJSVOptionsContext();
+    const rootLevel = renderRootTreeLines ? 0 : 1;
+    const isRootLevel = currentNestingLevel < rootLevel;
 
-  let ml: SpaceVals | undefined;
-  if (!isRootLevel) {
-    ml = currentNestingLevel === rootLevel ? 'px' : 4;
-  }
+    let ml: SpaceVals | undefined;
+    if (!isRootLevel) {
+      ml = currentNestingLevel === rootLevel ? 'px' : 7;
+    }
 
-  return (
-    
-      {childNodes.map((childNode: SchemaNode) => (
-        
-      ))}
-    
-  );
-};
+    return (
+      
+        {childNodes.map((childNode: SchemaNode) => (
+          
+        ))}
+      
+    );
+  },
+);
diff --git a/src/components/shared/Properties.tsx b/src/components/shared/Properties.tsx
index 6e594eb5..e012e97a 100644
--- a/src/components/shared/Properties.tsx
+++ b/src/components/shared/Properties.tsx
@@ -10,6 +10,14 @@ export interface IProperties {
   validations: Dictionary;
 }
 
+export const useHasProperties = ({ required, deprecated, validations: { readOnly, writeOnly } }: IProperties) => {
+  const { viewMode } = useJSVOptionsContext();
+
+  const showVisibilityValidations = viewMode === 'standalone' && !!readOnly !== !!writeOnly;
+
+  return deprecated || showVisibilityValidations || required;
+};
+
 export const Properties: React.FunctionComponent = ({
   required,
   deprecated,
diff --git a/src/components/shared/__tests__/Property.spec.tsx b/src/components/shared/__tests__/Property.spec.tsx
index cc652d21..1cf7860b 100644
--- a/src/components/shared/__tests__/Property.spec.tsx
+++ b/src/components/shared/__tests__/Property.spec.tsx
@@ -83,7 +83,7 @@ describe('Property component', () => {
 
       const wrapper = render(schema, ['properties', 'foo']);
       expect(wrapper.find(SchemaRow).html()).toMatchInlineSnapshot(
-        `""`,
+        `""`,
       );
     });
 
@@ -102,7 +102,7 @@ describe('Property component', () => {
 
       const wrapper = render(schema, ['properties', 'foo']);
       expect(wrapper.find(SchemaRow).html()).toMatchInlineSnapshot(
-        `""`,
+        `""`,
       );
     });
 
@@ -120,7 +120,7 @@ describe('Property component', () => {
 
       const wrapper = render(schema, ['items', 'properties', 'foo']);
       expect(wrapper.html()).toMatchInlineSnapshot(
-        `""`,
+        `""`,
       );
     });
 
@@ -141,7 +141,7 @@ describe('Property component', () => {
 
       const wrapper = render(schema);
       expect(wrapper.html()).toMatchInlineSnapshot(
-        `""`,
+        `""`,
       );
     });
 
@@ -164,7 +164,7 @@ describe('Property component', () => {
 
       const wrapper = render(schema);
       expect(wrapper.html()).toMatchInlineSnapshot(
-        `""`,
+        `""`,
       );
     });
 
@@ -199,12 +199,12 @@ describe('Property component', () => {
 
       let wrapper = render(schema, ['properties', 'array-all-objects', 'items', 'properties', 'foo']);
       expect(wrapper.html()).toMatchInlineSnapshot(
-        `""`,
+        `""`,
       );
 
       wrapper = render(schema, ['properties', 'array-all-objects', 'items', 'properties', 'bar']);
       expect(wrapper.html()).toMatchInlineSnapshot(
-        `""`,
+        `""`,
       );
     });
 
@@ -224,7 +224,7 @@ describe('Property component', () => {
 
       const wrapper = mount();
       expect(wrapper.html()).toMatchInlineSnapshot(
-        `""`,
+        `""`,
       );
       wrapper.unmount();
     });