diff --git a/apps/portal/src/components/resumes/browse/ResumeListItem.tsx b/apps/portal/src/components/resumes/browse/ResumeListItem.tsx
index 53c33e78..c7f7677a 100644
--- a/apps/portal/src/components/resumes/browse/ResumeListItem.tsx
+++ b/apps/portal/src/components/resumes/browse/ResumeListItem.tsx
@@ -1,4 +1,4 @@
-import { formatDistanceToNow } from 'date-fns';
+import formatDistanceToNow from 'date-fns/formatDistanceToNow';
 import Link from 'next/link';
 import type { UrlObject } from 'url';
 import { ChevronRightIcon } from '@heroicons/react/20/solid';
diff --git a/apps/portal/src/components/resumes/browse/resumeConstants.ts b/apps/portal/src/components/resumes/browse/resumeConstants.ts
index 6996538a..42d79249 100644
--- a/apps/portal/src/components/resumes/browse/resumeConstants.ts
+++ b/apps/portal/src/components/resumes/browse/resumeConstants.ts
@@ -4,10 +4,16 @@ export const BROWSE_TABS_VALUES = {
   STARRED: 'starred',
 };
 
-export const SORT_OPTIONS = [
-  { current: true, href: '#', name: 'Latest' },
-  { current: false, href: '#', name: 'Popular' },
-  { current: false, href: '#', name: 'Top Comments' },
+export type SortOrder = 'latest' | 'popular' | 'topComments';
+type SortOption = {
+  name: string;
+  value: SortOrder;
+};
+
+export const SORT_OPTIONS: Array<SortOption> = [
+  { name: 'Latest', value: 'latest' },
+  { name: 'Popular', value: 'popular' },
+  { name: 'Top Comments', value: 'topComments' },
 ];
 
 export const TOP_HITS = [
@@ -17,45 +23,46 @@ export const TOP_HITS = [
   { href: '#', name: 'US Only' },
 ];
 
-export const ROLES = [
+export type FilterOption = {
+  label: string;
+  value: string;
+};
+
+export const ROLE: Array<FilterOption> = [
   {
-    checked: false,
     label: 'Full-Stack Engineer',
     value: 'Full-Stack Engineer',
   },
-  { checked: false, label: 'Frontend Engineer', value: 'Frontend Engineer' },
-  { checked: false, label: 'Backend Engineer', value: 'Backend Engineer' },
-  { checked: false, label: 'DevOps Engineer', value: 'DevOps Engineer' },
-  { checked: false, label: 'iOS Engineer', value: 'iOS Engineer' },
-  { checked: false, label: 'Android Engineer', value: 'Android Engineer' },
+  { label: 'Frontend Engineer', value: 'Frontend Engineer' },
+  { label: 'Backend Engineer', value: 'Backend Engineer' },
+  { label: 'DevOps Engineer', value: 'DevOps Engineer' },
+  { label: 'iOS Engineer', value: 'iOS Engineer' },
+  { label: 'Android Engineer', value: 'Android Engineer' },
 ];
 
-export const EXPERIENCE = [
-  { checked: false, label: 'Freshman', value: 'Freshman' },
-  { checked: false, label: 'Sophomore', value: 'Sophomore' },
-  { checked: false, label: 'Junior', value: 'Junior' },
-  { checked: false, label: 'Senior', value: 'Senior' },
+export const EXPERIENCE: Array<FilterOption> = [
+  { label: 'Freshman', value: 'Freshman' },
+  { label: 'Sophomore', value: 'Sophomore' },
+  { label: 'Junior', value: 'Junior' },
+  { label: 'Senior', value: 'Senior' },
   {
-    checked: false,
     label: 'Fresh Grad (0-1 years)',
     value: 'Fresh Grad (0-1 years)',
   },
   {
-    checked: false,
     label: 'Mid-level (2 - 5 years)',
     value: 'Mid-level (2 - 5 years)',
   },
   {
-    checked: false,
     label: 'Senior (5+ years)',
     value: 'Senior (5+ years)',
   },
 ];
 
-export const LOCATION = [
-  { checked: false, label: 'Singapore', value: 'Singapore' },
-  { checked: false, label: 'United States', value: 'United States' },
-  { checked: false, label: 'India', value: 'India' },
+export const LOCATION: Array<FilterOption> = [
+  { label: 'Singapore', value: 'Singapore' },
+  { label: 'United States', value: 'United States' },
+  { label: 'India', value: 'India' },
 ];
 
 export const TEST_RESUMES = [
diff --git a/apps/portal/src/pages/resumes/[resumeId].tsx b/apps/portal/src/pages/resumes/[resumeId].tsx
index c888f6f3..40f7b630 100644
--- a/apps/portal/src/pages/resumes/[resumeId].tsx
+++ b/apps/portal/src/pages/resumes/[resumeId].tsx
@@ -4,7 +4,6 @@ import Error from 'next/error';
 import Head from 'next/head';
 import { useRouter } from 'next/router';
 import { useSession } from 'next-auth/react';
-import { useEffect, useState } from 'react';
 import {
   AcademicCapIcon,
   BriefcaseIcon,
@@ -27,6 +26,7 @@ export default function ResumeReviewPage() {
   const { data: session } = useSession();
   const router = useRouter();
   const { resumeId } = router.query;
+  const utils = trpc.useContext();
   // Safe to assert resumeId type as string because query is only sent if so
   const detailsQuery = trpc.useQuery(
     ['resumes.resume.findOne', { resumeId: resumeId as string }],
@@ -36,33 +36,14 @@ export default function ResumeReviewPage() {
   );
   const starMutation = trpc.useMutation('resumes.resume.star', {
     onSuccess() {
-      setStarDetails({
-        isStarred: true,
-        numStars: starDetails.numStars + 1,
-      });
+      utils.invalidateQueries(['resumes.resume.findOne']);
     },
   });
   const unstarMutation = trpc.useMutation('resumes.resume.unstar', {
     onSuccess() {
-      setStarDetails({
-        isStarred: false,
-        numStars: starDetails.numStars - 1,
-      });
+      utils.invalidateQueries(['resumes.resume.findOne']);
     },
   });
-  const [starDetails, setStarDetails] = useState({
-    isStarred: false,
-    numStars: 0,
-  });
-
-  useEffect(() => {
-    if (detailsQuery?.data !== undefined) {
-      setStarDetails({
-        isStarred: !!detailsQuery.data?.stars.length,
-        numStars: detailsQuery.data?._count.stars ?? 0,
-      });
-    }
-  }, [detailsQuery.data]);
 
   const onStarButtonClick = () => {
     if (session?.user?.id == null) {
@@ -72,7 +53,7 @@ export default function ResumeReviewPage() {
 
     // Star button only rendered if resume exists
     // Star button only clickable if user exists
-    if (starDetails.isStarred) {
+    if (detailsQuery.data?.stars.length) {
       unstarMutation.mutate({
         resumeId: resumeId as string,
       });
@@ -104,30 +85,37 @@ export default function ResumeReviewPage() {
               </h1>
               <button
                 className={clsx(
-                  starDetails.isStarred
+                  detailsQuery.data?.stars.length
                     ? 'z-10 border-indigo-500 outline-none ring-1 ring-indigo-500'
                     : '',
                   'isolate inline-flex max-h-10 items-center space-x-4 rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50  disabled:hover:bg-white',
                 )}
-                disabled={starMutation.isLoading || unstarMutation.isLoading}
-                id="star-button"
+                disabled={
+                  session?.user === undefined ||
+                  starMutation.isLoading ||
+                  unstarMutation.isLoading
+                }
                 type="button"
                 onClick={onStarButtonClick}>
                 <span className="relative inline-flex">
-                  <StarIcon
-                    aria-hidden="true"
-                    className={clsx(
-                      starDetails.isStarred
-                        ? 'text-orange-400'
-                        : 'text-gray-400',
-                      '-ml-1 mr-2 h-5 w-5',
+                  <div className="-ml-1 mr-2 h-5 w-5">
+                    {starMutation.isLoading || unstarMutation.isLoading ? (
+                      <Spinner className="mt-0.5" size="xs" />
+                    ) : (
+                      <StarIcon
+                        aria-hidden="true"
+                        className={clsx(
+                          detailsQuery.data?.stars.length
+                            ? 'text-orange-400'
+                            : 'text-gray-400',
+                        )}
+                      />
                     )}
-                    id="star-icon"
-                  />
+                  </div>
                   Star
                 </span>
                 <span className="relative -ml-px inline-flex">
-                  {starDetails.numStars}
+                  {detailsQuery.data?._count.stars}
                 </span>
               </button>
             </div>
diff --git a/apps/portal/src/pages/resumes/index.tsx b/apps/portal/src/pages/resumes/index.tsx
index a65f3ca6..d6631f4d 100644
--- a/apps/portal/src/pages/resumes/index.tsx
+++ b/apps/portal/src/pages/resumes/index.tsx
@@ -1,22 +1,28 @@
-import clsx from 'clsx';
+import compareAsc from 'date-fns/compareAsc';
 import Head from 'next/head';
 import { useRouter } from 'next/router';
 import { useSession } from 'next-auth/react';
-import { Fragment, useState } from 'react';
-import { Disclosure, Menu, Transition } from '@headlessui/react';
-import {
-  ChevronDownIcon,
-  MinusIcon,
-  PlusIcon,
-} from '@heroicons/react/20/solid';
+import { useState } from 'react';
+import { Disclosure } from '@headlessui/react';
+import { MinusIcon, PlusIcon } from '@heroicons/react/20/solid';
 import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
-import { Tabs, TextInput } from '@tih/ui';
+import {
+  CheckboxInput,
+  CheckboxList,
+  DropdownMenu,
+  Tabs,
+  TextInput,
+} from '@tih/ui';
 
+import type {
+  FilterOption,
+  SortOrder,
+} from '~/components/resumes/browse/resumeConstants';
 import {
   BROWSE_TABS_VALUES,
   EXPERIENCE,
   LOCATION,
-  ROLES,
+  ROLE,
   SORT_OPTIONS,
   TOP_HITS,
 } from '~/components/resumes/browse/resumeConstants';
@@ -29,11 +35,19 @@ import { trpc } from '~/utils/trpc';
 
 import type { Resume } from '~/types/resume';
 
-const filters = [
+type FilterId = 'experience' | 'location' | 'role';
+type Filter = {
+  id: FilterId;
+  name: string;
+  options: Array<FilterOption>;
+};
+type FilterState = Record<FilterId, Array<string>>;
+
+const filters: Array<Filter> = [
   {
-    id: 'roles',
-    name: 'Roles',
-    options: ROLES,
+    id: 'role',
+    name: 'Role',
+    options: ROLE,
   },
   {
     id: 'experience',
@@ -47,11 +61,47 @@ const filters = [
   },
 ];
 
+const INITIAL_FILTER_STATE: FilterState = {
+  experience: Object.values(EXPERIENCE).map(({ value }) => value),
+  location: Object.values(LOCATION).map(({ value }) => value),
+  role: Object.values(ROLE).map(({ value }) => value),
+};
+
+const filterResumes = (
+  resumes: Array<Resume>,
+  searchValue: string,
+  userFilters: FilterState,
+) =>
+  resumes
+    .filter((resume) =>
+      resume.title.toLowerCase().includes(searchValue.toLocaleLowerCase()),
+    )
+    .filter(
+      ({ experience, location, role }) =>
+        userFilters.role.includes(role) &&
+        userFilters.experience.includes(experience) &&
+        userFilters.location.includes(location),
+    );
+
+const sortComparators: Record<
+  SortOrder,
+  (resume1: Resume, resume2: Resume) => number
+> = {
+  latest: (resume1, resume2) =>
+    compareAsc(resume2.createdAt, resume1.createdAt),
+  popular: (resume1, resume2) => resume2.numStars - resume1.numStars,
+  topComments: (resume1, resume2) => resume2.numComments - resume1.numComments,
+};
+const sortResumes = (resumes: Array<Resume>, sortOrder: SortOrder) =>
+  resumes.sort(sortComparators[sortOrder]);
+
 export default function ResumeHomePage() {
   const { data: sessionData } = useSession();
   const router = useRouter();
   const [tabsValue, setTabsValue] = useState(BROWSE_TABS_VALUES.ALL);
+  const [sortOrder, setSortOrder] = useState(SORT_OPTIONS[0].value);
   const [searchValue, setSearchValue] = useState('');
+  const [userFilters, setUserFilters] = useState(INITIAL_FILTER_STATE);
   const [resumes, setResumes] = useState<Array<Resume>>([]);
   const [renderSignInButton, setRenderSignInButton] = useState(false);
   const [signInButtonText, setSignInButtonText] = useState('');
@@ -102,6 +152,26 @@ export default function ResumeHomePage() {
     }
   };
 
+  const onFilterCheckboxChange = (
+    isChecked: boolean,
+    filterSection: FilterId,
+    filterValue: string,
+  ) => {
+    if (isChecked) {
+      setUserFilters({
+        ...userFilters,
+        [filterSection]: [...userFilters[filterSection], filterValue],
+      });
+    } else {
+      setUserFilters({
+        ...userFilters,
+        [filterSection]: userFilters[filterSection].filter(
+          (value) => value !== filterValue,
+        ),
+      });
+    }
+  };
+
   return (
     <>
       <Head>
@@ -154,49 +224,17 @@ export default function ResumeHomePage() {
                     </form>
                   </div>
                   <div className="col-span-1 justify-self-center">
-                    <Menu as="div" className="relative inline-block text-left">
-                      <div>
-                        {/* TODO: Sort logic */}
-                        <Menu.Button className="group inline-flex justify-center text-sm font-medium text-gray-700 hover:text-gray-900">
-                          Sort
-                          <ChevronDownIcon
-                            aria-hidden="true"
-                            className="-mr-1 ml-1 h-5 w-5 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
-                          />
-                        </Menu.Button>
-                      </div>
-
-                      <Transition
-                        as={Fragment}
-                        enter="transition ease-out duration-100"
-                        enterFrom="transform opacity-0 scale-95"
-                        enterTo="transform opacity-100 scale-100"
-                        leave="transition ease-in duration-75"
-                        leaveFrom="transform opacity-100 scale-100"
-                        leaveTo="transform opacity-0 scale-95">
-                        <Menu.Items className="absolute right-0 z-10 mt-2 w-40 origin-top-right rounded-md bg-white shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none">
-                          <div className="py-1">
-                            {SORT_OPTIONS.map((option) => (
-                              <Menu.Item key={option.name}>
-                                {({ active }) => (
-                                  <a
-                                    className={clsx(
-                                      option.current
-                                        ? 'font-medium text-gray-900'
-                                        : 'text-gray-500',
-                                      active ? 'bg-gray-100' : '',
-                                      'block px-4 py-2 text-sm',
-                                    )}
-                                    href={option.href}>
-                                    {option.name}
-                                  </a>
-                                )}
-                              </Menu.Item>
-                            ))}
-                          </div>
-                        </Menu.Items>
-                      </Transition>
-                    </Menu>
+                    <DropdownMenu align="end" label="Sort">
+                      {SORT_OPTIONS.map((option) => (
+                        <DropdownMenu.Item
+                          key={option.name}
+                          isSelected={sortOrder === option.value}
+                          label={option.name}
+                          onClick={() =>
+                            setSortOrder(option.value)
+                          }></DropdownMenu.Item>
+                      ))}
+                    </DropdownMenu>
                   </div>
                   <div className="col-span-1">
                     <button
@@ -256,28 +294,32 @@ export default function ResumeHomePage() {
                                 </span>
                               </Disclosure.Button>
                             </h3>
-                            <Disclosure.Panel className="pt-6">
-                              <div className="space-y-4">
-                                {section.options.map((option, optionIdx) => (
+                            <Disclosure.Panel className="pt-4">
+                              <CheckboxList
+                                description=""
+                                isLabelHidden={true}
+                                label=""
+                                orientation="vertical">
+                                {section.options.map((option) => (
                                   <div
                                     key={option.value}
-                                    className="flex items-center">
-                                    <input
-                                      className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
-                                      defaultChecked={option.checked}
-                                      defaultValue={option.value}
-                                      id={`filter-${section.id}-${optionIdx}`}
-                                      name={`${section.id}[]`}
-                                      type="checkbox"
+                                    className="[&>div>div:nth-child(2)>label]:font-normal [&>div>div:nth-child(1)>input]:text-indigo-600 [&>div>div:nth-child(1)>input]:ring-indigo-500">
+                                    <CheckboxInput
+                                      label={option.label}
+                                      value={userFilters[section.id].includes(
+                                        option.value,
+                                      )}
+                                      onChange={(isChecked) =>
+                                        onFilterCheckboxChange(
+                                          isChecked,
+                                          section.id,
+                                          option.value,
+                                        )
+                                      }
                                     />
-                                    <label
-                                      className="ml-3 text-sm text-gray-600"
-                                      htmlFor={`filter-${section.id}-${optionIdx}`}>
-                                      {option.label}
-                                    </label>
                                   </div>
                                 ))}
-                              </div>
+                              </CheckboxList>
                             </Disclosure.Panel>
                           </>
                         )}
@@ -296,7 +338,10 @@ export default function ResumeHomePage() {
                     starredResumesQuery.isFetching ||
                     myResumesQuery.isFetching
                   }
-                  resumes={resumes}
+                  resumes={sortResumes(
+                    filterResumes(resumes, searchValue, userFilters),
+                    sortOrder,
+                  )}
                 />
               </div>
             </div>
diff --git a/apps/portal/src/pages/resumes/submit.tsx b/apps/portal/src/pages/resumes/submit.tsx
index b85852f0..79c1fc9b 100644
--- a/apps/portal/src/pages/resumes/submit.tsx
+++ b/apps/portal/src/pages/resumes/submit.tsx
@@ -11,7 +11,7 @@ import { Button, CheckboxInput, Select, TextArea, TextInput } from '@tih/ui';
 import {
   EXPERIENCE,
   LOCATION,
-  ROLES,
+  ROLE,
 } from '~/components/resumes/browse/resumeConstants';
 
 import { RESUME_STORAGE_KEY } from '~/constants/file-storage-keys';
@@ -152,7 +152,7 @@ export default function SubmitResumeForm() {
                   {...register('role', { required: true })}
                   disabled={isLoading}
                   label="Role"
-                  options={ROLES}
+                  options={ROLE}
                   required={true}
                   onChange={(val) => setValue('role', val)}
                 />